mirror of https://github.com/fantasticit/think.git
chore: format code
This commit is contained in:
parent
f629eedbb0
commit
6c716b0dc2
17
package.json
17
package.json
|
@ -20,11 +20,7 @@
|
|||
"pm2": "concurrently \"pnpm:pm2:*\"",
|
||||
"pm2:server": "pnpm run --dir packages/server pm2",
|
||||
"pm2:client": "pnpm run --dir packages/client pm2",
|
||||
"lint": "eslint . -c ./.eslintrc.js --fix --quiet 'packages/**/*.{ts,tsx,js,jsx}'",
|
||||
"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}'"
|
||||
"format": "prettier --write --parser typescript \"packages/**/*.ts?(x)\""
|
||||
},
|
||||
"dependencies": {
|
||||
"concurrently": "^7.0.0",
|
||||
|
@ -36,6 +32,17 @@
|
|||
"node": ">=16.5.0"
|
||||
},
|
||||
"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",
|
||||
"typescript": "^4.5.5"
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"extends": "next/core-web-vitals"
|
||||
}
|
|
@ -6,7 +6,6 @@
|
|||
"prebuild": "rimraf .next",
|
||||
"build": "next build",
|
||||
"start": "cross-env NODE_ENV=production next start -p 5002",
|
||||
"lint": "next lint",
|
||||
"pm2": "pm2 start npm --name @think/client -- start"
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -74,8 +73,6 @@
|
|||
"devDependencies": {
|
||||
"@types/node": "17.0.13",
|
||||
"@types/react": "17.0.38",
|
||||
"eslint": "8.8.0",
|
||||
"eslint-config-next": "12.0.10",
|
||||
"tsconfig-paths-webpack-plugin": "^3.5.2",
|
||||
"typescript": "4.5.5"
|
||||
}
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
import { Space, Typography } from "@douyinfe/semi-ui";
|
||||
import { IconLikeHeart } from "@douyinfe/semi-icons";
|
||||
import { Space, Typography } from '@douyinfe/semi-ui';
|
||||
import { IconLikeHeart } from '@douyinfe/semi-icons';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
export const Author = () => {
|
||||
return (
|
||||
<div style={{ padding: "16px 0", textAlign: "center" }}>
|
||||
<div style={{ padding: '16px 0', textAlign: 'center' }}>
|
||||
<Text>
|
||||
<Space>
|
||||
Develop by
|
||||
<Text link={{ href: "https://github.com/fantasticit/think" }}>
|
||||
fantasticit
|
||||
</Text>
|
||||
with <IconLikeHeart style={{ color: "red" }} />
|
||||
<Text link={{ href: 'https://github.com/fantasticit/think' }}>fantasticit</Text>
|
||||
with <IconLikeHeart style={{ color: 'red' }} />
|
||||
</Space>
|
||||
</Text>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from "react";
|
||||
import { Empty, Spin, Typography } from "@douyinfe/semi-ui";
|
||||
import React from 'react';
|
||||
import { Empty, Spin, Typography } from '@douyinfe/semi-ui';
|
||||
|
||||
type RenderProps = React.ReactNode | (() => React.ReactNode);
|
||||
|
||||
|
@ -18,11 +18,10 @@ const defaultLoading = () => {
|
|||
};
|
||||
|
||||
const defaultRenderError = (error) => {
|
||||
return <Text>{(error && error.message) || "未知错误"}</Text>;
|
||||
return <Text>{(error && error.message) || '未知错误'}</Text>;
|
||||
};
|
||||
|
||||
const runRender = (fn, ...args) =>
|
||||
typeof fn === "function" ? fn.apply(null, args) : fn;
|
||||
const runRender = (fn, ...args) => (typeof fn === 'function' ? fn.apply(null, args) : fn);
|
||||
|
||||
export const DataRender: React.FC<IProps> = ({
|
||||
loading,
|
||||
|
|
|
@ -1,17 +1,14 @@
|
|||
import React from "react";
|
||||
import { Button } from "@douyinfe/semi-ui";
|
||||
import { useToggle } from "hooks/useToggle";
|
||||
import { useQuery } from "hooks/useQuery";
|
||||
import { DocumentCreator as DocumenCreatorForm } from "components/document/create";
|
||||
import React from 'react';
|
||||
import { Button } from '@douyinfe/semi-ui';
|
||||
import { useToggle } from 'hooks/useToggle';
|
||||
import { useQuery } from 'hooks/useQuery';
|
||||
import { DocumentCreator as DocumenCreatorForm } from 'components/document/create';
|
||||
|
||||
interface IProps {
|
||||
onCreateDocument?: () => void;
|
||||
}
|
||||
|
||||
export const DocumentCreator: React.FC<IProps> = ({
|
||||
onCreateDocument,
|
||||
children,
|
||||
}) => {
|
||||
export const DocumentCreator: React.FC<IProps> = ({ onCreateDocument, children }) => {
|
||||
const { wikiId, docId } = useQuery<{ wikiId?: string; docId?: string }>();
|
||||
const [visible, toggleVisible] = useToggle(false);
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import React, { useCallback } from "react";
|
||||
import { Dropdown, Button, Typography, Space } from "@douyinfe/semi-ui";
|
||||
import { IconMore, IconStar, IconPlus } from "@douyinfe/semi-icons";
|
||||
import { DocumentLinkCopyer } from "components/document/link";
|
||||
import { DocumentDeletor } from "components/document/delete";
|
||||
import { DocumentCreator } from "components/document/create";
|
||||
import { DocumentStar } from "components/document/star";
|
||||
import { useToggle } from "hooks/useToggle";
|
||||
import React, { useCallback } from 'react';
|
||||
import { Dropdown, Button, Typography, Space } from '@douyinfe/semi-ui';
|
||||
import { IconMore, IconStar, IconPlus } from '@douyinfe/semi-icons';
|
||||
import { DocumentLinkCopyer } from 'components/document/link';
|
||||
import { DocumentDeletor } from 'components/document/delete';
|
||||
import { DocumentCreator } from 'components/document/create';
|
||||
import { DocumentStar } from 'components/document/star';
|
||||
import { useToggle } from 'hooks/useToggle';
|
||||
|
||||
interface IProps {
|
||||
wikiId: string;
|
||||
|
@ -65,8 +65,8 @@ export const DocumentActions: React.FC<IProps> = ({
|
|||
<IconStar
|
||||
style={{
|
||||
color: star
|
||||
? "rgba(var(--semi-amber-4), 1)"
|
||||
: "rgba(var(--semi-grey-3), 1)",
|
||||
? 'rgba(var(--semi-amber-4), 1)'
|
||||
: 'rgba(var(--semi-grey-3), 1)',
|
||||
}}
|
||||
/>
|
||||
{text}
|
||||
|
@ -80,22 +80,13 @@ export const DocumentActions: React.FC<IProps> = ({
|
|||
</Dropdown.Item>
|
||||
<Dropdown.Divider />
|
||||
<Dropdown.Item onClick={prevent}>
|
||||
<DocumentDeletor
|
||||
wikiId={wikiId}
|
||||
documentId={documentId}
|
||||
onDelete={onDelete}
|
||||
/>
|
||||
<DocumentDeletor wikiId={wikiId} documentId={documentId} onDelete={onDelete} />
|
||||
</Dropdown.Item>
|
||||
</Dropdown.Menu>
|
||||
}
|
||||
>
|
||||
{children || (
|
||||
<Button
|
||||
onClick={prevent}
|
||||
icon={<IconMore />}
|
||||
theme="borderless"
|
||||
type="tertiary"
|
||||
/>
|
||||
<Button onClick={prevent} icon={<IconMore />} theme="borderless" type="tertiary" />
|
||||
)}
|
||||
</Dropdown>
|
||||
{showCreateDocument && (
|
||||
|
|
|
@ -1,27 +1,18 @@
|
|||
import type { IDocument } from "@think/domains";
|
||||
import { useCallback } from "react";
|
||||
import Router from "next/router";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
Button,
|
||||
Space,
|
||||
Typography,
|
||||
Tooltip,
|
||||
Avatar,
|
||||
Skeleton,
|
||||
} 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";
|
||||
import type { IDocument } from '@think/domains';
|
||||
import { useCallback } from 'react';
|
||||
import Router from 'next/router';
|
||||
import Link from 'next/link';
|
||||
import { Button, Space, Typography, Tooltip, Avatar, Skeleton } 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;
|
||||
|
||||
export const DocumentCard: React.FC<{ document: IDocument }> = ({
|
||||
document,
|
||||
}) => {
|
||||
export const DocumentCard: React.FC<{ document: IDocument }> = ({ document }) => {
|
||||
const gotoEdit = useCallback(() => {
|
||||
Router.push(`/wiki/${document.wikiId}/document/${document.id}/edit`);
|
||||
}, [document]);
|
||||
|
@ -112,7 +103,7 @@ export const DocumentCardPlaceholder = () => {
|
|||
</main>
|
||||
<footer>
|
||||
<Text type="tertiary" size="small">
|
||||
<div style={{ display: "flex" }}>
|
||||
<div style={{ display: 'flex' }}>
|
||||
更新时间:
|
||||
<Skeleton.Paragraph rows={1} style={{ width: 100 }} />
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Modal,
|
||||
|
@ -14,14 +14,14 @@ import {
|
|||
Popconfirm,
|
||||
AvatarGroup,
|
||||
Avatar,
|
||||
} from "@douyinfe/semi-ui";
|
||||
import { IconUserAdd, IconDelete } from "@douyinfe/semi-icons";
|
||||
import { useUser } from "data/user";
|
||||
import { EventEmitter } from "helpers/event-emitter";
|
||||
import { useToggle } from "hooks/useToggle";
|
||||
import { useCollaborationDocument } from "data/document";
|
||||
import { DataRender } from "components/data-render";
|
||||
import { DocumentLinkCopyer } from "components/document/link";
|
||||
} from '@douyinfe/semi-ui';
|
||||
import { IconUserAdd, IconDelete } from '@douyinfe/semi-icons';
|
||||
import { useUser } from 'data/user';
|
||||
import { EventEmitter } from 'helpers/event-emitter';
|
||||
import { useToggle } from 'hooks/useToggle';
|
||||
import { useCollaborationDocument } from 'data/document';
|
||||
import { DataRender } from 'components/data-render';
|
||||
import { DocumentLinkCopyer } from 'components/document/link';
|
||||
|
||||
interface IProps {
|
||||
wikiId: string;
|
||||
|
@ -32,14 +32,13 @@ const { Paragraph } = Typography;
|
|||
const { Column } = Table;
|
||||
|
||||
const CollaborationEventEmitter = new EventEmitter();
|
||||
const KEY = "JOIN_USER";
|
||||
const KEY = 'JOIN_USER';
|
||||
|
||||
export const joinUser = (users) => {
|
||||
CollaborationEventEmitter.emit(KEY, users);
|
||||
};
|
||||
|
||||
const renderChecked =
|
||||
(onChange, authKey: "readable" | "editable") => (checked, docAuth) => {
|
||||
const renderChecked = (onChange, authKey: 'readable' | 'editable') => (checked, docAuth) => {
|
||||
const handle = (evt) => {
|
||||
const data = {
|
||||
...docAuth.auth,
|
||||
|
@ -49,31 +48,22 @@ const renderChecked =
|
|||
onChange(data);
|
||||
};
|
||||
|
||||
return (
|
||||
<Checkbox
|
||||
style={{ display: "inline-block" }}
|
||||
checked={checked}
|
||||
onChange={handle}
|
||||
/>
|
||||
);
|
||||
};
|
||||
return <Checkbox style={{ display: 'inline-block' }} checked={checked} onChange={handle} />;
|
||||
};
|
||||
|
||||
export const DocumentCollaboration: React.FC<IProps> = ({
|
||||
wikiId,
|
||||
documentId,
|
||||
}) => {
|
||||
export const DocumentCollaboration: React.FC<IProps> = ({ wikiId, documentId }) => {
|
||||
const { user: currentUser } = useUser();
|
||||
const [visible, toggleVisible] = useToggle(false);
|
||||
const { users, loading, error, addUser, updateUser, deleteUser } =
|
||||
useCollaborationDocument(documentId);
|
||||
const [inviteUser, setInviteUser] = useState("");
|
||||
const [inviteUser, setInviteUser] = useState('');
|
||||
|
||||
const [collaborationUsers, setCollaborationUsers] = useState([]);
|
||||
|
||||
const handleOk = () => {
|
||||
addUser(inviteUser).then(() => {
|
||||
Toast.success("添加成功");
|
||||
setInviteUser("");
|
||||
Toast.success('添加成功');
|
||||
setInviteUser('');
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -95,9 +85,7 @@ export const DocumentCollaboration: React.FC<IProps> = ({
|
|||
if (
|
||||
collaborationUsers.length === newCollaborationUsers.length &&
|
||||
newCollaborationUsers.every((newUser) => {
|
||||
return collaborationUsers.find(
|
||||
(existUser) => existUser.id === newUser.id
|
||||
);
|
||||
return collaborationUsers.find((existUser) => existUser.id === newUser.id);
|
||||
})
|
||||
) {
|
||||
return;
|
||||
|
@ -120,11 +108,7 @@ export const DocumentCollaboration: React.FC<IProps> = ({
|
|||
if (error)
|
||||
return (
|
||||
<Tooltip content="邀请他人协作" position="bottom">
|
||||
<Button
|
||||
theme="borderless"
|
||||
type="tertiary"
|
||||
icon={<IconUserAdd />}
|
||||
></Button>
|
||||
<Button theme="borderless" type="tertiary" icon={<IconUserAdd />}></Button>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
|
@ -150,13 +134,13 @@ export const DocumentCollaboration: React.FC<IProps> = ({
|
|||
></Button>
|
||||
</Tooltip>
|
||||
<Modal
|
||||
title={"文档协作"}
|
||||
okText={"邀请对方"}
|
||||
title={'文档协作'}
|
||||
okText={'邀请对方'}
|
||||
visible={visible}
|
||||
onOk={handleOk}
|
||||
onCancel={() => toggleVisible(false)}
|
||||
maskClosable={false}
|
||||
style={{ maxWidth: "96vw" }}
|
||||
style={{ maxWidth: '96vw' }}
|
||||
footer={null}
|
||||
>
|
||||
<Tabs type="line">
|
||||
|
@ -169,14 +153,14 @@ export const DocumentCollaboration: React.FC<IProps> = ({
|
|||
></Input>
|
||||
<Paragraph style={{ marginTop: 16 }}>
|
||||
邀请成功后,请将该链接发送给对方。
|
||||
<span style={{ verticalAlign: "middle" }}>
|
||||
<span style={{ verticalAlign: 'middle' }}>
|
||||
<DocumentLinkCopyer wikiId={wikiId} documentId={documentId} />
|
||||
</span>
|
||||
</Paragraph>
|
||||
<Button
|
||||
theme="solid"
|
||||
block
|
||||
style={{ margin: "24px 0" }}
|
||||
style={{ margin: '24px 0' }}
|
||||
disabled={!inviteUser}
|
||||
onClick={handleOk}
|
||||
>
|
||||
|
@ -190,25 +174,20 @@ export const DocumentCollaboration: React.FC<IProps> = ({
|
|||
error={error}
|
||||
loadingContent={<Spin />}
|
||||
normalContent={() => (
|
||||
<Table
|
||||
style={{ margin: "24px 0" }}
|
||||
dataSource={users}
|
||||
size="small"
|
||||
pagination
|
||||
>
|
||||
<Table style={{ margin: '24px 0' }} dataSource={users} size="small" pagination>
|
||||
<Column title="用户名" dataIndex="user.name" key="name" />
|
||||
<Column
|
||||
title="是否可读"
|
||||
dataIndex="auth.readable"
|
||||
key="readable"
|
||||
render={renderChecked(updateUser, "readable")}
|
||||
render={renderChecked(updateUser, 'readable')}
|
||||
align="center"
|
||||
/>
|
||||
<Column
|
||||
title="是否可编辑"
|
||||
dataIndex="auth.editable"
|
||||
key="editable"
|
||||
render={renderChecked(updateUser, "editable")}
|
||||
render={renderChecked(updateUser, 'editable')}
|
||||
align="center"
|
||||
/>
|
||||
<Column
|
||||
|
@ -221,11 +200,7 @@ export const DocumentCollaboration: React.FC<IProps> = ({
|
|||
title="确认删除该成员?"
|
||||
onConfirm={() => handleDelete(document)}
|
||||
>
|
||||
<Button
|
||||
type="tertiary"
|
||||
theme="borderless"
|
||||
icon={<IconDelete />}
|
||||
/>
|
||||
<Button type="tertiary" theme="borderless" icon={<IconDelete />} />
|
||||
</Popconfirm>
|
||||
)}
|
||||
/>
|
||||
|
|
|
@ -1,16 +1,10 @@
|
|||
import React from "react";
|
||||
import type { IComment, IUser } from "@think/domains";
|
||||
import {
|
||||
Avatar,
|
||||
Typography,
|
||||
Space,
|
||||
Popconfirm,
|
||||
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";
|
||||
import React from 'react';
|
||||
import type { IComment, IUser } from '@think/domains';
|
||||
import { Avatar, Typography, Space, Popconfirm, 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 {
|
||||
comment: IComment;
|
||||
|
@ -52,27 +46,15 @@ export const CommentItem: React.FC<IProps> = ({
|
|||
</main>
|
||||
<footer>
|
||||
<Space>
|
||||
<Text
|
||||
type="secondary"
|
||||
size="small"
|
||||
onClick={() => replyComment(comment)}
|
||||
>
|
||||
<Text type="secondary" size="small" onClick={() => replyComment(comment)}>
|
||||
回复
|
||||
</Text>
|
||||
{user && user.id === comment.createUserId && (
|
||||
<Text
|
||||
type="secondary"
|
||||
size="small"
|
||||
onClick={() => editComment(comment)}
|
||||
>
|
||||
<Text type="secondary" size="small" onClick={() => editComment(comment)}>
|
||||
编辑
|
||||
</Text>
|
||||
)}
|
||||
<Popconfirm
|
||||
showArrow
|
||||
title="确认删除该评论?"
|
||||
onConfirm={() => deleteComment(comment)}
|
||||
>
|
||||
<Popconfirm showArrow title="确认删除该评论?" onConfirm={() => deleteComment(comment)}>
|
||||
<Text type="secondary" size="small">
|
||||
删除
|
||||
</Text>
|
||||
|
@ -96,7 +78,7 @@ export const CommentItemPlaceholder = () => {
|
|||
</header>
|
||||
<main>
|
||||
<div>
|
||||
<Skeleton.Paragraph style={{ width: "100%" }} rows={3} />
|
||||
<Skeleton.Paragraph style={{ width: '100%' }} rows={3} />
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from "react";
|
||||
import type { IComment } from "@think/domains";
|
||||
import { CommentItem } from "./Item";
|
||||
import React from 'react';
|
||||
import type { IComment } from '@think/domains';
|
||||
import { CommentItem } from './Item';
|
||||
|
||||
interface IProps {
|
||||
comments: Array<IComment>;
|
||||
|
@ -11,18 +11,9 @@ interface IProps {
|
|||
|
||||
const PADDING_LEFT = 32;
|
||||
|
||||
const CommentInner = ({
|
||||
data,
|
||||
depth,
|
||||
replyComment,
|
||||
editComment,
|
||||
deleteComment,
|
||||
}) => {
|
||||
const CommentInner = ({ data, depth, replyComment, editComment, deleteComment }) => {
|
||||
return (
|
||||
<div
|
||||
key={"comment" + depth}
|
||||
style={{ paddingLeft: depth > 0 ? PADDING_LEFT : 0 }}
|
||||
>
|
||||
<div key={'comment' + depth} style={{ paddingLeft: depth > 0 ? PADDING_LEFT : 0 }}>
|
||||
{(data || []).map((item) => {
|
||||
const hasChildren = item.children && item.children.length;
|
||||
return (
|
||||
|
@ -36,7 +27,7 @@ const CommentInner = ({
|
|||
></CommentItem>
|
||||
{hasChildren ? (
|
||||
<CommentInner
|
||||
key={"comment-inner" + depth}
|
||||
key={'comment-inner' + depth}
|
||||
data={item.children}
|
||||
depth={depth + 1}
|
||||
replyComment={replyComment}
|
||||
|
@ -59,7 +50,7 @@ export const Comments: React.FC<IProps> = ({
|
|||
}) => {
|
||||
return (
|
||||
<CommentInner
|
||||
key={"root-menu"}
|
||||
key={'root-menu'}
|
||||
data={comments}
|
||||
depth={0}
|
||||
replyComment={replyComment}
|
||||
|
|
|
@ -1,21 +1,14 @@
|
|||
import React, { useRef, useState } from "react";
|
||||
import { useEditor, EditorContent } from "@tiptap/react";
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
Space,
|
||||
Typography,
|
||||
Banner,
|
||||
Pagination,
|
||||
} from "@douyinfe/semi-ui";
|
||||
import { useToggle } from "hooks/useToggle";
|
||||
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";
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { useEditor, EditorContent } from '@tiptap/react';
|
||||
import { Avatar, Button, Space, Typography, Banner, Pagination } from '@douyinfe/semi-ui';
|
||||
import { useToggle } from 'hooks/useToggle';
|
||||
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 {
|
||||
documentId: string;
|
||||
|
@ -140,9 +133,7 @@ export const CommentEditor: React.FC<IProps> = ({ documentId }) => {
|
|||
title={<Text>回复评论: {replyComment.createUser.name}</Text>}
|
||||
description={
|
||||
<Paragraph ellipsis={{ rows: 2 }}>
|
||||
<div
|
||||
dangerouslySetInnerHTML={{ __html: replyComment.html }}
|
||||
></div>
|
||||
<div dangerouslySetInnerHTML={{ __html: replyComment.html }}></div>
|
||||
</Paragraph>
|
||||
}
|
||||
onClose={handleClose}
|
||||
|
@ -176,7 +167,7 @@ export const CommentEditor: React.FC<IProps> = ({ documentId }) => {
|
|||
{isEdit ? (
|
||||
<>
|
||||
<div className={styles.editorWrap}>
|
||||
<div style={{ width: "100%", overflow: "auto" }}>
|
||||
<div style={{ width: '100%', overflow: 'auto' }}>
|
||||
<CommentMenuBar editor={editor} />
|
||||
</div>
|
||||
<div className={styles.innerWrap}>
|
||||
|
@ -188,11 +179,7 @@ export const CommentEditor: React.FC<IProps> = ({ documentId }) => {
|
|||
<Button theme="solid" type="primary" onClick={save}>
|
||||
保存
|
||||
</Button>
|
||||
<Button
|
||||
theme="borderless"
|
||||
type="tertiary"
|
||||
onClick={handleClose}
|
||||
>
|
||||
<Button theme="borderless" type="tertiary" onClick={handleClose}>
|
||||
取消
|
||||
</Button>
|
||||
</Space>
|
||||
|
|
|
@ -1,18 +1,12 @@
|
|||
import {
|
||||
Dispatch,
|
||||
SetStateAction,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
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 { Dispatch, SetStateAction, useCallback, useEffect, useState } from 'react';
|
||||
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 {
|
||||
wikiId: string;
|
||||
|
@ -31,7 +25,7 @@ export const DocumentCreator: React.FC<IProps> = ({
|
|||
}) => {
|
||||
const { loading, create } = useCreateDocument();
|
||||
const [createChildDoc, setCreateChildDoc] = useState(false);
|
||||
const [templateId, setTemplateId] = useState("");
|
||||
const [templateId, setTemplateId] = useState('');
|
||||
|
||||
const handleOk = () => {
|
||||
const data = {
|
||||
|
@ -42,7 +36,7 @@ export const DocumentCreator: React.FC<IProps> = ({
|
|||
create(data).then((res) => {
|
||||
toggleVisible(false);
|
||||
onCreate && onCreate();
|
||||
setTemplateId("");
|
||||
setTemplateId('');
|
||||
Router.push({
|
||||
pathname: `/wiki/${wikiId}/document/${res.id}/edit`,
|
||||
});
|
||||
|
@ -64,12 +58,12 @@ export const DocumentCreator: React.FC<IProps> = ({
|
|||
onCancel={handleCancel}
|
||||
okButtonProps={{ loading }}
|
||||
style={{
|
||||
maxWidth: "96vw",
|
||||
width: "calc(80vw - 120px)",
|
||||
maxWidth: '96vw',
|
||||
width: 'calc(80vw - 120px)',
|
||||
}}
|
||||
bodyStyle={{
|
||||
maxHeight: "calc(90vh - 120px)",
|
||||
overflow: "auto",
|
||||
maxHeight: 'calc(90vh - 120px)',
|
||||
overflow: 'auto',
|
||||
}}
|
||||
key={wikiId}
|
||||
>
|
||||
|
@ -94,7 +88,7 @@ export const DocumentCreator: React.FC<IProps> = ({
|
|||
firstListItem={
|
||||
<TemplateCardEmpty
|
||||
getClassNames={() => !templateId && styles.isActive}
|
||||
onClick={() => setTemplateId("")}
|
||||
onClick={() => setTemplateId('')}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
@ -107,7 +101,7 @@ export const DocumentCreator: React.FC<IProps> = ({
|
|||
firstListItem={
|
||||
<TemplateCardEmpty
|
||||
getClassNames={() => !templateId && styles.isActive}
|
||||
onClick={() => setTemplateId("")}
|
||||
onClick={() => setTemplateId('')}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import React, { useCallback } from "react";
|
||||
import Router from "next/router";
|
||||
import { Typography, Space, Modal } from "@douyinfe/semi-ui";
|
||||
import { IconDelete } from "@douyinfe/semi-icons";
|
||||
import { useDeleteDocument } from "data/document";
|
||||
import React, { useCallback } from 'react';
|
||||
import Router from 'next/router';
|
||||
import { Typography, Space, Modal } from '@douyinfe/semi-ui';
|
||||
import { IconDelete } from '@douyinfe/semi-icons';
|
||||
import { useDeleteDocument } from 'data/document';
|
||||
|
||||
interface IProps {
|
||||
wikiId: string;
|
||||
|
@ -12,16 +12,12 @@ interface IProps {
|
|||
|
||||
const { Text } = Typography;
|
||||
|
||||
export const DocumentDeletor: React.FC<IProps> = ({
|
||||
wikiId,
|
||||
documentId,
|
||||
onDelete,
|
||||
}) => {
|
||||
export const DocumentDeletor: React.FC<IProps> = ({ wikiId, documentId, onDelete }) => {
|
||||
const { deleteDocument: api, loading } = useDeleteDocument(documentId);
|
||||
|
||||
const deleteAction = useCallback(() => {
|
||||
Modal.error({
|
||||
title: "确定删除吗?",
|
||||
title: '确定删除吗?',
|
||||
content: <Text>文档删除后不可恢复!</Text>,
|
||||
onOk: () => {
|
||||
api().then(() => {
|
||||
|
@ -32,8 +28,8 @@ export const DocumentDeletor: React.FC<IProps> = ({
|
|||
});
|
||||
});
|
||||
},
|
||||
okButtonProps: { loading, type: "danger" },
|
||||
style: { maxWidth: "96vw" },
|
||||
okButtonProps: { loading, type: 'danger' },
|
||||
style: { maxWidth: '96vw' },
|
||||
});
|
||||
}, [wikiId, documentId, api, loading, onDelete]);
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import React, { useMemo, useEffect } from "react";
|
||||
import cls from "classnames";
|
||||
import { useEditor, EditorContent } from "@tiptap/react";
|
||||
import { Layout, Nav, BackTop, Toast } from "@douyinfe/semi-ui";
|
||||
import { ILoginUser, IAuthority } from "@think/domains";
|
||||
import { useToggle } from "hooks/useToggle";
|
||||
import React, { useMemo, useEffect } from 'react';
|
||||
import cls from 'classnames';
|
||||
import { useEditor, EditorContent } from '@tiptap/react';
|
||||
import { Layout, Nav, BackTop, Toast } from '@douyinfe/semi-ui';
|
||||
import { ILoginUser, IAuthority } from '@think/domains';
|
||||
import { useToggle } from 'hooks/useToggle';
|
||||
import {
|
||||
DEFAULT_EXTENSION,
|
||||
DocumentWithTitle,
|
||||
|
@ -13,10 +13,10 @@ import {
|
|||
destoryProvider,
|
||||
MenuBar,
|
||||
Toc,
|
||||
} from "components/tiptap";
|
||||
import { DataRender } from "components/data-render";
|
||||
import { joinUser } from "components/document/collaboration";
|
||||
import styles from "./index.module.scss";
|
||||
} from 'components/tiptap';
|
||||
import { DataRender } from 'components/data-render';
|
||||
import { joinUser } from 'components/document/collaboration';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const { Header, Content } = Layout;
|
||||
|
||||
|
@ -28,22 +28,16 @@ interface IProps {
|
|||
style: React.CSSProperties;
|
||||
}
|
||||
|
||||
export const Editor: React.FC<IProps> = ({
|
||||
user,
|
||||
documentId,
|
||||
authority,
|
||||
className,
|
||||
style,
|
||||
}) => {
|
||||
export const Editor: React.FC<IProps> = ({ user, documentId, authority, className, style }) => {
|
||||
if (!user) return null;
|
||||
|
||||
const provider = useMemo(() => {
|
||||
return getProvider({
|
||||
targetId: documentId,
|
||||
token: user.token,
|
||||
cacheType: "EDITOR",
|
||||
cacheType: 'EDITOR',
|
||||
user,
|
||||
docType: "document",
|
||||
docType: 'document',
|
||||
events: {
|
||||
onAwarenessUpdate({ states }) {
|
||||
joinUser({ states });
|
||||
|
@ -63,16 +57,16 @@ export const Editor: React.FC<IProps> = ({
|
|||
const [loading, toggleLoading] = useToggle(true);
|
||||
|
||||
useEffect(() => {
|
||||
provider.on("synced", () => {
|
||||
provider.on('synced', () => {
|
||||
toggleLoading(false);
|
||||
});
|
||||
|
||||
provider.on("status", async ({ status }) => {
|
||||
console.log("status", status);
|
||||
provider.on('status', async ({ status }) => {
|
||||
console.log('status', status);
|
||||
});
|
||||
|
||||
return () => {
|
||||
destoryProvider(provider, "EDITOR");
|
||||
destoryProvider(provider, 'EDITOR');
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
@ -92,11 +86,7 @@ export const Editor: React.FC<IProps> = ({
|
|||
<div className={cls(styles.contentWrap, className)}>
|
||||
<EditorContent editor={editor} />
|
||||
</div>
|
||||
<BackTop
|
||||
target={() =>
|
||||
document.querySelector("#js-template-editor-container")
|
||||
}
|
||||
/>
|
||||
<BackTop target={() => document.querySelector('#js-template-editor-container')} />
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Router from "next/router";
|
||||
import React, { useCallback, useMemo } from "react";
|
||||
import Router from 'next/router';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import {
|
||||
Layout,
|
||||
Nav,
|
||||
|
@ -10,20 +10,20 @@ import {
|
|||
Tooltip,
|
||||
Spin,
|
||||
Popover,
|
||||
} from "@douyinfe/semi-ui";
|
||||
import { IconChevronLeft, IconArticle } from "@douyinfe/semi-icons";
|
||||
import { useUser } from "data/user";
|
||||
import { useDocumentDetail } from "data/document";
|
||||
import { Seo } from "components/seo";
|
||||
import { Theme } from "components/theme";
|
||||
import { DataRender } from "components/data-render";
|
||||
import { DocumentShare } from "components/document/share";
|
||||
import { DocumentStar } from "components/document/star";
|
||||
import { DocumentCollaboration } from "components/document/collaboration";
|
||||
import { DocumentStyle } from "components/document/style";
|
||||
import { useDocumentStyle } from "hooks/useDocumentStyle";
|
||||
import { Editor } from "./editor";
|
||||
import styles from "./index.module.scss";
|
||||
} from '@douyinfe/semi-ui';
|
||||
import { IconChevronLeft, IconArticle } from '@douyinfe/semi-icons';
|
||||
import { useUser } from 'data/user';
|
||||
import { useDocumentDetail } from 'data/document';
|
||||
import { Seo } from 'components/seo';
|
||||
import { Theme } from 'components/theme';
|
||||
import { DataRender } from 'components/data-render';
|
||||
import { DocumentShare } from 'components/document/share';
|
||||
import { DocumentStar } from 'components/document/star';
|
||||
import { DocumentCollaboration } from 'components/document/collaboration';
|
||||
import { DocumentStyle } from 'components/document/style';
|
||||
import { useDocumentStyle } from 'hooks/useDocumentStyle';
|
||||
import { Editor } from './editor';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const { Header, Content } = Layout;
|
||||
const { Text } = Typography;
|
||||
|
@ -37,9 +37,7 @@ export const DocumentEditor: React.FC<IProps> = ({ documentId }) => {
|
|||
|
||||
const { width, fontSize } = useDocumentStyle();
|
||||
const editorWrapClassNames = useMemo(() => {
|
||||
return width === "standardWidth"
|
||||
? styles.isStandardWidth
|
||||
: styles.isFullWidth;
|
||||
return width === 'standardWidth' ? styles.isStandardWidth : styles.isFullWidth;
|
||||
}, [width]);
|
||||
|
||||
const { user } = useUser();
|
||||
|
@ -59,11 +57,7 @@ export const DocumentEditor: React.FC<IProps> = ({ documentId }) => {
|
|||
const DocumentTitle = (
|
||||
<>
|
||||
<Tooltip content="返回" position="bottom">
|
||||
<Button
|
||||
onClick={goback}
|
||||
icon={<IconChevronLeft />}
|
||||
style={{ marginRight: 16 }}
|
||||
/>
|
||||
<Button onClick={goback} icon={<IconChevronLeft />} style={{ marginRight: 16 }} />
|
||||
</Tooltip>
|
||||
<DataRender
|
||||
loading={docAuthLoading}
|
||||
|
@ -71,9 +65,7 @@ export const DocumentEditor: React.FC<IProps> = ({ documentId }) => {
|
|||
loadingContent={
|
||||
<Skeleton
|
||||
active
|
||||
placeholder={
|
||||
<Skeleton.Title style={{ width: 80, marginBottom: 8 }} />
|
||||
}
|
||||
placeholder={<Skeleton.Title style={{ width: 80, marginBottom: 8 }} />}
|
||||
loading={true}
|
||||
/>
|
||||
}
|
||||
|
@ -104,17 +96,8 @@ export const DocumentEditor: React.FC<IProps> = ({ documentId }) => {
|
|||
)}
|
||||
<DocumentShare key="share" documentId={documentId} />
|
||||
<DocumentStar key="star" documentId={documentId} />
|
||||
<Popover
|
||||
key="style"
|
||||
zIndex={1061}
|
||||
position="bottomLeft"
|
||||
content={<DocumentStyle />}
|
||||
>
|
||||
<Button
|
||||
icon={<IconArticle />}
|
||||
theme="borderless"
|
||||
type="tertiary"
|
||||
/>
|
||||
<Popover key="style" zIndex={1061} position="bottomLeft" content={<DocumentStyle />}>
|
||||
<Button icon={<IconArticle />} theme="borderless" type="tertiary" />
|
||||
</Popover>
|
||||
<Theme />
|
||||
</Space>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import React, { useCallback } from "react";
|
||||
import { Typography, Space } from "@douyinfe/semi-ui";
|
||||
import { IconLink } from "@douyinfe/semi-icons";
|
||||
import { copy } from "helpers/copy";
|
||||
import { buildUrl } from "helpers/url";
|
||||
import React, { useCallback } from 'react';
|
||||
import { Typography, Space } from '@douyinfe/semi-ui';
|
||||
import { IconLink } from '@douyinfe/semi-icons';
|
||||
import { copy } from 'helpers/copy';
|
||||
import { buildUrl } from 'helpers/url';
|
||||
|
||||
interface IProps {
|
||||
wikiId: string;
|
||||
|
@ -11,16 +11,13 @@ interface IProps {
|
|||
|
||||
const { Text } = Typography;
|
||||
|
||||
export const DocumentLinkCopyer: React.FC<IProps> = ({
|
||||
wikiId,
|
||||
documentId,
|
||||
}) => {
|
||||
export const DocumentLinkCopyer: React.FC<IProps> = ({ wikiId, documentId }) => {
|
||||
const handle = useCallback(() => {
|
||||
copy(buildUrl(`/wiki/${wikiId}/document/${documentId}`));
|
||||
}, [wikiId, documentId]);
|
||||
|
||||
return (
|
||||
<Text onClick={handle} style={{ cursor: "pointer" }}>
|
||||
<Text onClick={handle} style={{ cursor: 'pointer' }}>
|
||||
<Space>
|
||||
<IconLink />
|
||||
复制链接
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import React from "react";
|
||||
import { useEditor, EditorContent } from "@tiptap/react";
|
||||
import { Layout } from "@douyinfe/semi-ui";
|
||||
import { IDocument } from "@think/domains";
|
||||
import { DEFAULT_EXTENSION, DocumentWithTitle } from "components/tiptap";
|
||||
import { safeJSONParse } from "helpers/json";
|
||||
import React from 'react';
|
||||
import { useEditor, EditorContent } from '@tiptap/react';
|
||||
import { Layout } from '@douyinfe/semi-ui';
|
||||
import { IDocument } from '@think/domains';
|
||||
import { DEFAULT_EXTENSION, DocumentWithTitle } from 'components/tiptap';
|
||||
import { safeJSONParse } from 'helpers/json';
|
||||
|
||||
interface IProps {
|
||||
document: IDocument;
|
||||
|
@ -15,7 +15,7 @@ export const DocumentContent: React.FC<IProps> = ({ document }) => {
|
|||
|
||||
if (json && json.content) {
|
||||
json = {
|
||||
type: "doc",
|
||||
type: 'doc',
|
||||
content: json.content.slice(1),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import React, { useMemo, useEffect } from "react";
|
||||
import { useEditor, EditorContent } from "@tiptap/react";
|
||||
import { Layout } from "@douyinfe/semi-ui";
|
||||
import { ILoginUser } from "@think/domains";
|
||||
import { useToggle } from "hooks/useToggle";
|
||||
import React, { useMemo, useEffect } from 'react';
|
||||
import { useEditor, EditorContent } from '@tiptap/react';
|
||||
import { Layout } from '@douyinfe/semi-ui';
|
||||
import { ILoginUser } from '@think/domains';
|
||||
import { useToggle } from 'hooks/useToggle';
|
||||
import {
|
||||
DEFAULT_EXTENSION,
|
||||
DocumentWithTitle,
|
||||
|
@ -10,10 +10,10 @@ import {
|
|||
getCollaborationCursorExtension,
|
||||
getProvider,
|
||||
destoryProvider,
|
||||
} from "components/tiptap";
|
||||
import { DataRender } from "components/data-render";
|
||||
import { joinUser } from "components/document/collaboration";
|
||||
import styles from "./index.module.scss";
|
||||
} from 'components/tiptap';
|
||||
import { DataRender } from 'components/data-render';
|
||||
import { joinUser } from 'components/document/collaboration';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const { Content } = Layout;
|
||||
|
||||
|
@ -29,9 +29,9 @@ export const Editor: React.FC<IProps> = ({ user, documentId }) => {
|
|||
return getProvider({
|
||||
targetId: documentId,
|
||||
token: user.token,
|
||||
cacheType: "READER",
|
||||
cacheType: 'READER',
|
||||
user,
|
||||
docType: "document",
|
||||
docType: 'document',
|
||||
events: {
|
||||
onAwarenessUpdate({ states }) {
|
||||
joinUser({ states });
|
||||
|
@ -51,12 +51,12 @@ export const Editor: React.FC<IProps> = ({ user, documentId }) => {
|
|||
const [loading, toggleLoading] = useToggle(true);
|
||||
|
||||
useEffect(() => {
|
||||
provider.on("synced", () => {
|
||||
provider.on('synced', () => {
|
||||
toggleLoading(false);
|
||||
});
|
||||
|
||||
return () => {
|
||||
destoryProvider(provider, "READER");
|
||||
destoryProvider(provider, 'READER');
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import Router from "next/router";
|
||||
import React, { useCallback, useMemo } from "react";
|
||||
import cls from "classnames";
|
||||
import Router from 'next/router';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import cls from 'classnames';
|
||||
import {
|
||||
Layout,
|
||||
Nav,
|
||||
|
@ -11,22 +11,22 @@ import {
|
|||
Tooltip,
|
||||
Popover,
|
||||
BackTop,
|
||||
} from "@douyinfe/semi-ui";
|
||||
import { IconEdit, IconArticle } from "@douyinfe/semi-icons";
|
||||
import { Seo } from "components/seo";
|
||||
import { DataRender } from "components/data-render";
|
||||
import { DocumentShare } from "components/document/share";
|
||||
import { DocumentStar } from "components/document/star";
|
||||
import { DocumentCollaboration } from "components/document/collaboration";
|
||||
import { DocumentStyle } from "components/document/style";
|
||||
import { CommentEditor } from "components/document/comments";
|
||||
import { useDocumentStyle } from "hooks/useDocumentStyle";
|
||||
import { useUser } from "data/user";
|
||||
import { useDocumentDetail } from "data/document";
|
||||
import { DocumentSkeleton } from "components/tiptap";
|
||||
import { Editor } from "./editor";
|
||||
import { CreateUser } from "./user";
|
||||
import styles from "./index.module.scss";
|
||||
} from '@douyinfe/semi-ui';
|
||||
import { IconEdit, IconArticle } from '@douyinfe/semi-icons';
|
||||
import { Seo } from 'components/seo';
|
||||
import { DataRender } from 'components/data-render';
|
||||
import { DocumentShare } from 'components/document/share';
|
||||
import { DocumentStar } from 'components/document/star';
|
||||
import { DocumentCollaboration } from 'components/document/collaboration';
|
||||
import { DocumentStyle } from 'components/document/style';
|
||||
import { CommentEditor } from 'components/document/comments';
|
||||
import { useDocumentStyle } from 'hooks/useDocumentStyle';
|
||||
import { useUser } from 'data/user';
|
||||
import { useDocumentDetail } from 'data/document';
|
||||
import { DocumentSkeleton } from 'components/tiptap';
|
||||
import { Editor } from './editor';
|
||||
import { CreateUser } from './user';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const { Header } = Layout;
|
||||
const { Text } = Typography;
|
||||
|
@ -39,9 +39,7 @@ export const DocumentReader: React.FC<IProps> = ({ documentId }) => {
|
|||
if (!documentId) return null;
|
||||
const { width, fontSize } = useDocumentStyle();
|
||||
const editorWrapClassNames = useMemo(() => {
|
||||
return width === "standardWidth"
|
||||
? styles.isStandardWidth
|
||||
: styles.isFullWidth;
|
||||
return width === 'standardWidth' ? styles.isStandardWidth : styles.isFullWidth;
|
||||
}, [width]);
|
||||
|
||||
const { user } = useUser();
|
||||
|
@ -61,7 +59,7 @@ export const DocumentReader: React.FC<IProps> = ({ documentId }) => {
|
|||
<div className={styles.wrap}>
|
||||
<Header className={styles.headerWrap}>
|
||||
<Nav
|
||||
style={{ overflow: "auto", paddingLeft: 0, paddingRight: 0 }}
|
||||
style={{ overflow: 'auto', paddingLeft: 0, paddingRight: 0 }}
|
||||
mode="horizontal"
|
||||
header={
|
||||
<DataRender
|
||||
|
@ -70,18 +68,12 @@ export const DocumentReader: React.FC<IProps> = ({ documentId }) => {
|
|||
loadingContent={
|
||||
<Skeleton
|
||||
active
|
||||
placeholder={
|
||||
<Skeleton.Title style={{ width: 80, marginBottom: 8 }} />
|
||||
}
|
||||
placeholder={<Skeleton.Title style={{ width: 80, marginBottom: 8 }} />}
|
||||
loading={true}
|
||||
/>
|
||||
}
|
||||
normalContent={() => (
|
||||
<Text
|
||||
strong
|
||||
ellipsis={{ showTooltip: true }}
|
||||
style={{ width: 120 }}
|
||||
>
|
||||
<Text strong ellipsis={{ showTooltip: true }} style={{ width: 120 }}>
|
||||
{document.title}
|
||||
</Text>
|
||||
)}
|
||||
|
@ -107,27 +99,15 @@ export const DocumentReader: React.FC<IProps> = ({ documentId }) => {
|
|||
<DocumentStar key="star" documentId={documentId} />
|
||||
</>
|
||||
)}
|
||||
<Popover
|
||||
key="style"
|
||||
zIndex={1061}
|
||||
position="bottomLeft"
|
||||
content={<DocumentStyle />}
|
||||
>
|
||||
<Button
|
||||
icon={<IconArticle />}
|
||||
theme="borderless"
|
||||
type="tertiary"
|
||||
/>
|
||||
<Popover key="style" zIndex={1061} position="bottomLeft" content={<DocumentStyle />}>
|
||||
<Button icon={<IconArticle />} theme="borderless" type="tertiary" />
|
||||
</Popover>
|
||||
</Space>
|
||||
}
|
||||
></Nav>
|
||||
</Header>
|
||||
<Layout className={styles.contentWrap}>
|
||||
<div
|
||||
className={cls(styles.editorWrap, editorWrapClassNames)}
|
||||
style={{ fontSize }}
|
||||
>
|
||||
<div className={cls(styles.editorWrap, editorWrapClassNames)} style={{ fontSize }}>
|
||||
<DataRender
|
||||
loading={docAuthLoading}
|
||||
error={docAuthError}
|
||||
|
@ -136,20 +116,14 @@ export const DocumentReader: React.FC<IProps> = ({ documentId }) => {
|
|||
return (
|
||||
<>
|
||||
<Seo title={document.title} />
|
||||
<Editor
|
||||
key={document.id}
|
||||
user={user}
|
||||
documentId={document.id}
|
||||
/>
|
||||
<Editor key={document.id} user={user} documentId={document.id} />
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<CreateUser document={document} />
|
||||
</div>
|
||||
<div className={styles.commentWrap}>
|
||||
<CommentEditor documentId={document.id} />
|
||||
</div>
|
||||
<BackTop
|
||||
target={() => window.document.querySelector(".Pane2")}
|
||||
/>
|
||||
<BackTop target={() => window.document.querySelector('.Pane2')} />
|
||||
</>
|
||||
);
|
||||
}}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useMemo, useEffect } from "react";
|
||||
import cls from "classnames";
|
||||
import React, { useMemo, useEffect } from 'react';
|
||||
import cls from 'classnames';
|
||||
import {
|
||||
Layout,
|
||||
Nav,
|
||||
|
@ -11,20 +11,20 @@ import {
|
|||
Popover,
|
||||
Modal,
|
||||
BackTop,
|
||||
} from "@douyinfe/semi-ui";
|
||||
import { IconArticle } from "@douyinfe/semi-icons";
|
||||
import { Seo } from "components/seo";
|
||||
import { LogoImage, LogoText } from "components/logo";
|
||||
import { DataRender } from "components/data-render";
|
||||
import { DocumentStyle } from "components/document/style";
|
||||
import { User } from "components/user";
|
||||
import { Theme } from "components/theme";
|
||||
import { useDocumentStyle } from "hooks/useDocumentStyle";
|
||||
import { usePublicDocument } from "data/document";
|
||||
import { DocumentSkeleton } from "components/tiptap";
|
||||
import { DocumentContent } from "../content";
|
||||
import { CreateUser } from "../user";
|
||||
import styles from "./index.module.scss";
|
||||
} from '@douyinfe/semi-ui';
|
||||
import { IconArticle } from '@douyinfe/semi-icons';
|
||||
import { Seo } from 'components/seo';
|
||||
import { LogoImage, LogoText } from 'components/logo';
|
||||
import { DataRender } from 'components/data-render';
|
||||
import { DocumentStyle } from 'components/document/style';
|
||||
import { User } from 'components/user';
|
||||
import { Theme } from 'components/theme';
|
||||
import { useDocumentStyle } from 'hooks/useDocumentStyle';
|
||||
import { usePublicDocument } from 'data/document';
|
||||
import { DocumentSkeleton } from 'components/tiptap';
|
||||
import { DocumentContent } from '../content';
|
||||
import { CreateUser } from '../user';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const { Header, Content } = Layout;
|
||||
const { Text, Title } = Typography;
|
||||
|
@ -34,29 +34,24 @@ interface IProps {
|
|||
hideLogo?: boolean;
|
||||
}
|
||||
|
||||
export const DocumentPublicReader: React.FC<IProps> = ({
|
||||
documentId,
|
||||
hideLogo = true,
|
||||
}) => {
|
||||
export const DocumentPublicReader: React.FC<IProps> = ({ documentId, hideLogo = true }) => {
|
||||
if (!documentId) return null;
|
||||
|
||||
const { data, loading, error, query } = usePublicDocument(documentId);
|
||||
|
||||
const { width, fontSize } = useDocumentStyle();
|
||||
const editorWrapClassNames = useMemo(() => {
|
||||
return width === "standardWidth"
|
||||
? styles.isStandardWidth
|
||||
: styles.isFullWidth;
|
||||
return width === 'standardWidth' ? styles.isStandardWidth : styles.isFullWidth;
|
||||
}, [width]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!error) return;
|
||||
if (error.statusCode !== 400) return;
|
||||
Modal.confirm({
|
||||
title: "请输入密码",
|
||||
title: '请输入密码',
|
||||
content: (
|
||||
<>
|
||||
<Seo title={"输入密码后查看"} />
|
||||
<Seo title={'输入密码后查看'} />
|
||||
<Input
|
||||
id="js-share-document-password"
|
||||
style={{ marginTop: 24 }}
|
||||
|
@ -70,9 +65,7 @@ export const DocumentPublicReader: React.FC<IProps> = ({
|
|||
hasCancel: false,
|
||||
maskClosable: false,
|
||||
onOk() {
|
||||
const $input = document.querySelector(
|
||||
"#js-share-document-password"
|
||||
) as HTMLInputElement;
|
||||
const $input = document.querySelector('#js-share-document-password') as HTMLInputElement;
|
||||
query($input.value);
|
||||
},
|
||||
});
|
||||
|
@ -93,17 +86,8 @@ export const DocumentPublicReader: React.FC<IProps> = ({
|
|||
}
|
||||
footer={
|
||||
<Space>
|
||||
<Popover
|
||||
key="style"
|
||||
zIndex={1061}
|
||||
position="bottomLeft"
|
||||
content={<DocumentStyle />}
|
||||
>
|
||||
<Button
|
||||
icon={<IconArticle />}
|
||||
theme="borderless"
|
||||
type="tertiary"
|
||||
/>
|
||||
<Popover key="style" zIndex={1061} position="bottomLeft" content={<DocumentStyle />}>
|
||||
<Button icon={<IconArticle />} theme="borderless" type="tertiary" />
|
||||
</Popover>
|
||||
<Theme />
|
||||
<User />
|
||||
|
@ -116,18 +100,12 @@ export const DocumentPublicReader: React.FC<IProps> = ({
|
|||
loadingContent={
|
||||
<Skeleton
|
||||
active
|
||||
placeholder={
|
||||
<Skeleton.Title style={{ width: 80, marginBottom: 8 }} />
|
||||
}
|
||||
placeholder={<Skeleton.Title style={{ width: 80, marginBottom: 8 }} />}
|
||||
loading={true}
|
||||
/>
|
||||
}
|
||||
normalContent={() => (
|
||||
<Text
|
||||
strong
|
||||
ellipsis={{ showTooltip: true }}
|
||||
style={{ width: 120 }}
|
||||
>
|
||||
<Text strong ellipsis={{ showTooltip: true }} style={{ width: 120 }}>
|
||||
{data.title}
|
||||
</Text>
|
||||
)}
|
||||
|
@ -139,10 +117,7 @@ export const DocumentPublicReader: React.FC<IProps> = ({
|
|||
loading={loading}
|
||||
error={error}
|
||||
loadingContent={
|
||||
<div
|
||||
className={cls(styles.editorWrap, editorWrapClassNames)}
|
||||
style={{ fontSize }}
|
||||
>
|
||||
<div className={cls(styles.editorWrap, editorWrapClassNames)} style={{ fontSize }}>
|
||||
<DocumentSkeleton />
|
||||
</div>
|
||||
}
|
||||
|
@ -156,16 +131,14 @@ export const DocumentPublicReader: React.FC<IProps> = ({
|
|||
id="js-share-document-editor-container"
|
||||
>
|
||||
<Title>{data.title}</Title>
|
||||
<div style={{ margin: "24px 0" }}>
|
||||
<div style={{ margin: '24px 0' }}>
|
||||
<CreateUser document={data} />
|
||||
</div>
|
||||
<DocumentContent document={data} />
|
||||
</div>
|
||||
<BackTop
|
||||
target={() =>
|
||||
document.querySelector(
|
||||
"#js-share-document-editor-container"
|
||||
).parentNode
|
||||
document.querySelector('#js-share-document-editor-container').parentNode
|
||||
}
|
||||
/>
|
||||
</>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Space, Typography, Avatar } from "@douyinfe/semi-ui";
|
||||
import { IconUser } from "@douyinfe/semi-icons";
|
||||
import { IDocument } from "@think/domains";
|
||||
import { LocaleTime } from "components/locale-time";
|
||||
import { Space, Typography, Avatar } from '@douyinfe/semi-ui';
|
||||
import { IconUser } from '@douyinfe/semi-icons';
|
||||
import { IDocument } from '@think/domains';
|
||||
import { LocaleTime } from 'components/locale-time';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
|
@ -11,10 +11,7 @@ export const CreateUser: React.FC<{ document: IDocument }> = ({ document }) => {
|
|||
return (
|
||||
<Text type="tertiary" size="small">
|
||||
<Space>
|
||||
<Avatar
|
||||
size="extra-extra-small"
|
||||
src={document.createUser && document.createUser.avatar}
|
||||
>
|
||||
<Avatar size="extra-extra-small" src={document.createUser && document.createUser.avatar}>
|
||||
<IconUser />
|
||||
</Avatar>
|
||||
<div>
|
||||
|
@ -25,7 +22,7 @@ export const CreateUser: React.FC<{ document: IDocument }> = ({ document }) => {
|
|||
<p>
|
||||
最近更新日期:
|
||||
<LocaleTime date={document.updatedAt} timeago />
|
||||
{" ⦁ "}阅读量:
|
||||
{' ⦁ '}阅读量:
|
||||
{document.views}
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
@ -1,19 +1,16 @@
|
|||
import React, { useMemo, useState, useEffect } from "react";
|
||||
import { Button, Modal, Input, Typography, Toast } from "@douyinfe/semi-ui";
|
||||
import { IconLink } from "@douyinfe/semi-icons";
|
||||
import { isPublicDocument } from "@think/domains";
|
||||
import { getDocumentShareURL } from "helpers/url";
|
||||
import { ShareIllustration } from "illustrations/share";
|
||||
import { DataRender } from "components/data-render";
|
||||
import { useToggle } from "hooks/useToggle";
|
||||
import { useDocumentDetail } from "data/document";
|
||||
import React, { useMemo, useState, useEffect } from 'react';
|
||||
import { Button, Modal, Input, Typography, Toast } from '@douyinfe/semi-ui';
|
||||
import { IconLink } from '@douyinfe/semi-icons';
|
||||
import { isPublicDocument } from '@think/domains';
|
||||
import { getDocumentShareURL } from 'helpers/url';
|
||||
import { ShareIllustration } from 'illustrations/share';
|
||||
import { DataRender } from 'components/data-render';
|
||||
import { useToggle } from 'hooks/useToggle';
|
||||
import { useDocumentDetail } from 'data/document';
|
||||
|
||||
interface IProps {
|
||||
documentId: string;
|
||||
render?: (arg: {
|
||||
isPublic: boolean;
|
||||
toggleVisible: (arg: boolean) => void;
|
||||
}) => React.ReactNode;
|
||||
render?: (arg: { isPublic: boolean; toggleVisible: (arg: boolean) => void }) => React.ReactNode;
|
||||
}
|
||||
|
||||
const { Text } = Typography;
|
||||
|
@ -21,18 +18,12 @@ const { Text } = Typography;
|
|||
export const DocumentShare: React.FC<IProps> = ({ documentId, render }) => {
|
||||
const [visible, toggleVisible] = useToggle(false);
|
||||
const { data, loading, error, toggleStatus } = useDocumentDetail(documentId);
|
||||
const [sharePassword, setSharePassword] = useState("");
|
||||
const isPublic = useMemo(
|
||||
() => data && isPublicDocument(data.document.status),
|
||||
[data]
|
||||
);
|
||||
const shareUrl = useMemo(
|
||||
() => data && getDocumentShareURL(data.document.id),
|
||||
[data]
|
||||
);
|
||||
const [sharePassword, setSharePassword] = useState('');
|
||||
const isPublic = useMemo(() => data && isPublicDocument(data.document.status), [data]);
|
||||
const shareUrl = useMemo(() => data && getDocumentShareURL(data.document.id), [data]);
|
||||
|
||||
const handleOk = () => {
|
||||
toggleStatus({ sharePassword: isPublic ? "" : sharePassword });
|
||||
toggleStatus({ sharePassword: isPublic ? '' : sharePassword });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -46,18 +37,18 @@ export const DocumentShare: React.FC<IProps> = ({ documentId, render }) => {
|
|||
render({ isPublic, toggleVisible })
|
||||
) : (
|
||||
<Button type="primary" theme="light" onClick={toggleVisible}>
|
||||
{isPublic ? "分享中" : "分享"}
|
||||
{isPublic ? '分享中' : '分享'}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Modal
|
||||
title={isPublic ? "关闭分享" : "开启分享"}
|
||||
okText={isPublic ? "关闭分享" : "开启分享"}
|
||||
title={isPublic ? '关闭分享' : '开启分享'}
|
||||
okText={isPublic ? '关闭分享' : '开启分享'}
|
||||
visible={visible}
|
||||
onOk={handleOk}
|
||||
onCancel={() => toggleVisible(false)}
|
||||
maskClosable={false}
|
||||
style={{ maxWidth: "96vw" }}
|
||||
style={{ maxWidth: '96vw' }}
|
||||
>
|
||||
<DataRender
|
||||
loading={loading}
|
||||
|
@ -65,7 +56,7 @@ export const DocumentShare: React.FC<IProps> = ({ documentId, render }) => {
|
|||
normalContent={() => {
|
||||
return (
|
||||
<div>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<ShareIllustration />
|
||||
</div>
|
||||
{isPublic ? (
|
||||
|
@ -73,7 +64,7 @@ export const DocumentShare: React.FC<IProps> = ({ documentId, render }) => {
|
|||
ellipsis
|
||||
icon={<IconLink />}
|
||||
copyable={{
|
||||
onCopy: () => Toast.success({ content: "复制文本成功" }),
|
||||
onCopy: () => Toast.success({ content: '复制文本成功' }),
|
||||
}}
|
||||
style={{
|
||||
width: 320,
|
||||
|
@ -93,8 +84,8 @@ export const DocumentShare: React.FC<IProps> = ({ documentId, render }) => {
|
|||
<div style={{ marginTop: 16 }}>
|
||||
<Text type="tertiary">
|
||||
{isPublic
|
||||
? "分享开启后,该页面包含的所有内容均可访问,请谨慎开启"
|
||||
: " 分享关闭后,其他人将不能继续访问该页面"}
|
||||
? '分享开启后,该页面包含的所有内容均可访问,请谨慎开启'
|
||||
: ' 分享关闭后,其他人将不能继续访问该页面'}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from "react";
|
||||
import { Typography, Tooltip, Button } from "@douyinfe/semi-ui";
|
||||
import { IconStar } from "@douyinfe/semi-icons";
|
||||
import { useDocumentStar } from "data/document";
|
||||
import React from 'react';
|
||||
import { Typography, Tooltip, Button } from '@douyinfe/semi-ui';
|
||||
import { IconStar } from '@douyinfe/semi-icons';
|
||||
import { useDocumentStar } from 'data/document';
|
||||
|
||||
interface IProps {
|
||||
documentId: string;
|
||||
|
@ -16,7 +16,7 @@ const { Text } = Typography;
|
|||
|
||||
export const DocumentStar: React.FC<IProps> = ({ documentId, render }) => {
|
||||
const { data, toggleStar } = useDocumentStar(documentId);
|
||||
const text = data ? "取消收藏" : "收藏文档";
|
||||
const text = data ? '取消收藏' : '收藏文档';
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -28,9 +28,7 @@ export const DocumentStar: React.FC<IProps> = ({ documentId, render }) => {
|
|||
icon={<IconStar />}
|
||||
theme="borderless"
|
||||
style={{
|
||||
color: data
|
||||
? "rgba(var(--semi-amber-4), 1)"
|
||||
: "rgba(var(--semi-grey-3), 1)",
|
||||
color: data ? 'rgba(var(--semi-amber-4), 1)' : 'rgba(var(--semi-grey-3), 1)',
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from "react";
|
||||
import { RadioGroup, Radio, Typography, Slider } from "@douyinfe/semi-ui";
|
||||
import { useDocumentStyle } from "hooks/useDocumentStyle";
|
||||
import styles from "./index.module.scss";
|
||||
import React from 'react';
|
||||
import { RadioGroup, Radio, Typography, Slider } from '@douyinfe/semi-ui';
|
||||
import { useDocumentStyle } from 'hooks/useDocumentStyle';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
|
@ -12,7 +12,7 @@ export const DocumentStyle = () => {
|
|||
<div className={styles.wrap}>
|
||||
<div className={styles.item}>
|
||||
<Text>正文大小</Text>
|
||||
<Text style={{ fontSize: "0.8em" }}> {fontSize}px</Text>
|
||||
<Text style={{ fontSize: '0.8em' }}> {fontSize}px</Text>
|
||||
<Slider
|
||||
min={12}
|
||||
max={24}
|
||||
|
@ -29,10 +29,10 @@ export const DocumentStyle = () => {
|
|||
type="button"
|
||||
value={width}
|
||||
onChange={(e) => setWidth(e.target.value)}
|
||||
style={{ marginTop: "0.5em" }}
|
||||
style={{ marginTop: '0.5em' }}
|
||||
>
|
||||
<Radio value={"standardWidth"}>标宽模式</Radio>
|
||||
<Radio value={"fullWidth"}>超宽模式</Radio>
|
||||
<Radio value={'standardWidth'}>标宽模式</Radio>
|
||||
<Radio value={'fullWidth'}>超宽模式</Radio>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from "react";
|
||||
import { Typography } from "@douyinfe/semi-ui";
|
||||
import React from 'react';
|
||||
import { Typography } from '@douyinfe/semi-ui';
|
||||
|
||||
interface IProps {
|
||||
illustration?: React.ReactNode;
|
||||
|
@ -12,16 +12,14 @@ export const Empty: React.FC<IProps> = ({ illustration = null, message }) => {
|
|||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
flexDirection: "column",
|
||||
margin: "16px 0",
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'column',
|
||||
margin: '16px 0',
|
||||
}}
|
||||
>
|
||||
{illustration && (
|
||||
<main style={{ textAlign: "center" }}>{illustration}</main>
|
||||
)}
|
||||
<footer style={{ textAlign: "center" }}>
|
||||
{illustration && <main style={{ textAlign: 'center' }}>{illustration}</main>}
|
||||
<footer style={{ textAlign: 'center' }}>
|
||||
<Text type="tertiary">{message}</Text>
|
||||
</footer>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from "react";
|
||||
import { MouseEventHandler } from "react";
|
||||
import React, { MouseEventHandler } from 'react';
|
||||
|
||||
type CellProperties = {
|
||||
active: boolean;
|
||||
|
@ -16,21 +15,21 @@ const getBaseStyles = (cellSize) => ({
|
|||
cell: {
|
||||
width: cellSize,
|
||||
height: cellSize,
|
||||
background: "#fff",
|
||||
cursor: "pointer",
|
||||
background: '#fff',
|
||||
cursor: 'pointer',
|
||||
borderRadius: 3,
|
||||
border: "1px solid #bababa",
|
||||
border: '1px solid #bababa',
|
||||
},
|
||||
active: {
|
||||
border: "1px solid #4d6cdd",
|
||||
background: "#4d6cdd",
|
||||
border: '1px solid #4d6cdd',
|
||||
background: '#4d6cdd',
|
||||
},
|
||||
hover: {
|
||||
border: "1px solid #fff",
|
||||
background: "#4d6cdd",
|
||||
border: '1px solid #fff',
|
||||
background: '#4d6cdd',
|
||||
},
|
||||
disabled: {
|
||||
filter: "brightness(0.7)",
|
||||
filter: 'brightness(0.7)',
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -51,10 +50,10 @@ export const GridCell = ({
|
|||
}: CellProperties) => {
|
||||
const baseStyles = getBaseStyles(cellSize);
|
||||
const cellStyles = {
|
||||
cell: getMergedStyle(baseStyles, styles, "cell"),
|
||||
active: getMergedStyle(baseStyles, styles, "active"),
|
||||
hover: getMergedStyle(baseStyles, styles, "hover"),
|
||||
disabled: getMergedStyle(baseStyles, styles, "disabled"),
|
||||
cell: getMergedStyle(baseStyles, styles, 'cell'),
|
||||
active: getMergedStyle(baseStyles, styles, 'active'),
|
||||
hover: getMergedStyle(baseStyles, styles, 'hover'),
|
||||
disabled: getMergedStyle(baseStyles, styles, 'disabled'),
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import React, { useMemo } from "react";
|
||||
import { useState, useCallback } from "react";
|
||||
import { Typography } from "@douyinfe/semi-ui";
|
||||
import { debounce } from "helpers/debounce";
|
||||
import { GridCell } from "./grid-cell";
|
||||
import React, { useMemo, useState, useCallback } from 'react';
|
||||
import { Typography } from '@douyinfe/semi-ui';
|
||||
import { debounce } from 'helpers/debounce';
|
||||
import { GridCell } from './grid-cell';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
|
@ -28,12 +27,12 @@ type CoordsType = {
|
|||
|
||||
const getBaseStyles = (cols, cellSize) => ({
|
||||
grid: {
|
||||
position: "relative",
|
||||
display: "grid",
|
||||
color: "#444",
|
||||
margin: "8px 0",
|
||||
gridGap: "4px 6px",
|
||||
gridTemplateColumns: Array(cols).fill(`${cellSize}px`).join(" "),
|
||||
position: 'relative',
|
||||
display: 'grid',
|
||||
color: '#444',
|
||||
margin: '8px 0',
|
||||
gridGap: '4px 6px',
|
||||
gridTemplateColumns: Array(cols).fill(`${cellSize}px`).join(' '),
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -88,8 +87,8 @@ export const GridSelect = ({
|
|||
const isCellDisabled = disabled;
|
||||
cells.push(
|
||||
<GridCell
|
||||
id={x + "-" + y}
|
||||
key={x + "-" + y}
|
||||
id={x + '-' + y}
|
||||
key={x + '-' + y}
|
||||
onClick={() => onClick({ x, y, isCellDisabled })}
|
||||
onMouseEnter={onHover.bind(null, { x, y, isCellDisabled })}
|
||||
active={isActive}
|
||||
|
@ -115,10 +114,7 @@ export const GridSelect = ({
|
|||
onHover,
|
||||
]);
|
||||
|
||||
const baseStyles = useMemo(
|
||||
() => getBaseStyles(cols, cellSize),
|
||||
[cols, cellSize]
|
||||
);
|
||||
const baseStyles = useMemo(() => getBaseStyles(cols, cellSize), [cols, cellSize]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -133,10 +129,8 @@ export const GridSelect = ({
|
|||
>
|
||||
{cells}
|
||||
</div>
|
||||
<footer style={{ textAlign: "center" }}>
|
||||
<Text>
|
||||
{hoverCell ? `${hoverCell.y + 1} x ${hoverCell.x + 1}` : null}
|
||||
</Text>
|
||||
<footer style={{ textAlign: 'center' }}>
|
||||
<Text>{hoverCell ? `${hoverCell.y + 1} x ${hoverCell.x + 1}` : null}</Text>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1 +1 @@
|
|||
export * from "./grid-select";
|
||||
export * from './grid-select';
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { Icon } from "@douyinfe/semi-ui";
|
||||
import { Icon } from '@douyinfe/semi-ui';
|
||||
|
||||
export const IconAddColumnAfter: React.FC<{ style?: React.CSSProperties }> = ({
|
||||
style = {},
|
||||
}) => {
|
||||
export const IconAddColumnAfter: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
|
||||
return (
|
||||
<Icon
|
||||
style={style}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { Icon } from "@douyinfe/semi-ui";
|
||||
import { Icon } from '@douyinfe/semi-ui';
|
||||
|
||||
export const IconAddColumnBefore: React.FC<{ style?: React.CSSProperties }> = ({
|
||||
style = {},
|
||||
}) => {
|
||||
export const IconAddColumnBefore: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
|
||||
return (
|
||||
<Icon
|
||||
style={style}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { Icon } from "@douyinfe/semi-ui";
|
||||
import { Icon } from '@douyinfe/semi-ui';
|
||||
|
||||
export const IconAddRowAfter: React.FC<{ style?: React.CSSProperties }> = ({
|
||||
style = {},
|
||||
}) => {
|
||||
export const IconAddRowAfter: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
|
||||
return (
|
||||
<Icon
|
||||
style={style}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { Icon } from "@douyinfe/semi-ui";
|
||||
import { Icon } from '@douyinfe/semi-ui';
|
||||
|
||||
export const IconAddRowBefore: React.FC<{ style?: React.CSSProperties }> = ({
|
||||
style = {},
|
||||
}) => {
|
||||
export const IconAddRowBefore: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
|
||||
return (
|
||||
<Icon
|
||||
style={style}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { Icon } from "@douyinfe/semi-ui";
|
||||
import { Icon } from '@douyinfe/semi-ui';
|
||||
|
||||
export const IconAttachment: React.FC<{ style?: React.CSSProperties }> = ({
|
||||
style = {},
|
||||
}) => {
|
||||
export const IconAttachment: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
|
||||
return (
|
||||
<Icon
|
||||
style={style}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { Icon } from "@douyinfe/semi-ui";
|
||||
import { Icon } from '@douyinfe/semi-ui';
|
||||
|
||||
export const IconCenter: React.FC<{ style?: React.CSSProperties }> = ({
|
||||
style = {},
|
||||
}) => {
|
||||
export const IconCenter: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
|
||||
return (
|
||||
<Icon
|
||||
style={style}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { Icon } from "@douyinfe/semi-ui";
|
||||
import { Icon } from '@douyinfe/semi-ui';
|
||||
|
||||
export const IconClear: React.FC<{ style?: React.CSSProperties }> = ({
|
||||
style = {},
|
||||
}) => {
|
||||
export const IconClear: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
|
||||
return (
|
||||
<Icon
|
||||
style={style}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { Icon } from "@douyinfe/semi-ui";
|
||||
import { Icon } from '@douyinfe/semi-ui';
|
||||
|
||||
export const IconCodeBlock: React.FC<{ style?: React.CSSProperties }> = ({
|
||||
style = {},
|
||||
}) => {
|
||||
export const IconCodeBlock: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
|
||||
return (
|
||||
<Icon
|
||||
style={style}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { Icon } from "@douyinfe/semi-ui";
|
||||
import { Icon } from '@douyinfe/semi-ui';
|
||||
|
||||
export const IconDeleteColumn: React.FC<{ style?: React.CSSProperties }> = ({
|
||||
style = {},
|
||||
}) => {
|
||||
export const IconDeleteColumn: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
|
||||
return (
|
||||
<Icon
|
||||
style={style}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { Icon } from "@douyinfe/semi-ui";
|
||||
import { Icon } from '@douyinfe/semi-ui';
|
||||
|
||||
export const IconDeleteRow: React.FC<{ style?: React.CSSProperties }> = ({
|
||||
style = {},
|
||||
}) => {
|
||||
export const IconDeleteRow: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
|
||||
return (
|
||||
<Icon
|
||||
style={style}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { Icon } from "@douyinfe/semi-ui";
|
||||
import { Icon } from '@douyinfe/semi-ui';
|
||||
|
||||
export const IconDeleteTable: React.FC<{ style?: React.CSSProperties }> = ({
|
||||
style = {},
|
||||
}) => {
|
||||
export const IconDeleteTable: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
|
||||
return (
|
||||
<Icon
|
||||
style={style}
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import React from "react";
|
||||
import { Icon } from "@douyinfe/semi-ui";
|
||||
import React from 'react';
|
||||
import { Icon } from '@douyinfe/semi-ui';
|
||||
|
||||
export const IconDocument: React.FC<{ style?: React.CSSProperties }> = ({
|
||||
style = {},
|
||||
}) => {
|
||||
export const IconDocument: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
|
||||
return (
|
||||
<Icon
|
||||
style={style}
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import React from "react";
|
||||
import { Icon } from "@douyinfe/semi-ui";
|
||||
import React from 'react';
|
||||
import { Icon } from '@douyinfe/semi-ui';
|
||||
|
||||
export const IconDocumentFill: React.FC<{ style?: React.CSSProperties }> = ({
|
||||
style = {},
|
||||
}) => {
|
||||
export const IconDocumentFill: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
|
||||
return (
|
||||
<Icon
|
||||
style={style}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { Icon } from "@douyinfe/semi-ui";
|
||||
import { Icon } from '@douyinfe/semi-ui';
|
||||
|
||||
export const IconEmoji: React.FC<{ style?: React.CSSProperties }> = ({
|
||||
style = {},
|
||||
}) => {
|
||||
export const IconEmoji: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
|
||||
return (
|
||||
<Icon
|
||||
style={style}
|
||||
|
|
|
@ -1,18 +1,11 @@
|
|||
import { Icon } from "@douyinfe/semi-ui";
|
||||
import { Icon } from '@douyinfe/semi-ui';
|
||||
|
||||
export const IconFont: React.FC<{ style?: React.CSSProperties }> = ({
|
||||
style = {},
|
||||
}) => {
|
||||
export const IconFont: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
|
||||
return (
|
||||
<Icon
|
||||
style={style}
|
||||
svg={
|
||||
<svg
|
||||
role="presentation"
|
||||
width="24"
|
||||
height="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<svg role="presentation" width="24" height="24" xmlns="http://www.w3.org/2000/svg">
|
||||
<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"
|
||||
fill="currentColor"
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { Icon } from "@douyinfe/semi-ui";
|
||||
import { Icon } from '@douyinfe/semi-ui';
|
||||
|
||||
export const IconFull: React.FC<{ style?: React.CSSProperties }> = ({
|
||||
style = {},
|
||||
}) => {
|
||||
export const IconFull: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
|
||||
return (
|
||||
<Icon
|
||||
style={style}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { Icon } from "@douyinfe/semi-ui";
|
||||
import { Icon } from '@douyinfe/semi-ui';
|
||||
|
||||
export const IconHalf: React.FC<{ style?: React.CSSProperties }> = ({
|
||||
style = {},
|
||||
}) => {
|
||||
export const IconHalf: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
|
||||
return (
|
||||
<Icon
|
||||
style={style}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { Icon } from "@douyinfe/semi-ui";
|
||||
import { Icon } from '@douyinfe/semi-ui';
|
||||
|
||||
export const IconImage: React.FC<{ style?: React.CSSProperties }> = ({
|
||||
style = {},
|
||||
}) => {
|
||||
export const IconImage: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
|
||||
return (
|
||||
<Icon
|
||||
style={style}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { Icon } from "@douyinfe/semi-ui";
|
||||
import { Icon } from '@douyinfe/semi-ui';
|
||||
|
||||
export const IconInfo: React.FC<{ style?: React.CSSProperties }> = ({
|
||||
style = {},
|
||||
}) => {
|
||||
export const IconInfo: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
|
||||
return (
|
||||
<Icon
|
||||
style={style}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { Icon } from "@douyinfe/semi-ui";
|
||||
import { Icon } from '@douyinfe/semi-ui';
|
||||
|
||||
export const IconLeft: React.FC<{ style?: React.CSSProperties }> = ({
|
||||
style = {},
|
||||
}) => {
|
||||
export const IconLeft: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
|
||||
return (
|
||||
<Icon
|
||||
style={style}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { Icon } from "@douyinfe/semi-ui";
|
||||
import { Icon } from '@douyinfe/semi-ui';
|
||||
|
||||
export const IconLink: React.FC<{ style?: React.CSSProperties }> = ({
|
||||
style = {},
|
||||
}) => {
|
||||
export const IconLink: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
|
||||
return (
|
||||
<Icon
|
||||
style={style}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { Icon } from "@douyinfe/semi-ui";
|
||||
import { Icon } from '@douyinfe/semi-ui';
|
||||
|
||||
export const IconMath: React.FC<{ style?: React.CSSProperties }> = ({
|
||||
style = {},
|
||||
}) => {
|
||||
export const IconMath: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
|
||||
return (
|
||||
<Icon
|
||||
style={style}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { Icon } from "@douyinfe/semi-ui";
|
||||
import { Icon } from '@douyinfe/semi-ui';
|
||||
|
||||
export const IconMergeCell: React.FC<{ style?: React.CSSProperties }> = ({
|
||||
style = {},
|
||||
}) => {
|
||||
export const IconMergeCell: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
|
||||
return (
|
||||
<Icon
|
||||
style={style}
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import React from "react";
|
||||
import { Icon } from "@douyinfe/semi-ui";
|
||||
import React from 'react';
|
||||
import { Icon } from '@douyinfe/semi-ui';
|
||||
|
||||
export const IconMessage: React.FC<{ style?: React.CSSProperties }> = ({
|
||||
style = {},
|
||||
}) => {
|
||||
export const IconMessage: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
|
||||
return (
|
||||
<Icon
|
||||
style={style}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { Icon } from "@douyinfe/semi-ui";
|
||||
import { Icon } from '@douyinfe/semi-ui';
|
||||
|
||||
export const IconMind: React.FC<{ style?: React.CSSProperties }> = ({
|
||||
style = {},
|
||||
}) => {
|
||||
export const IconMind: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
|
||||
return (
|
||||
<Icon
|
||||
style={style}
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import React from "react";
|
||||
import { Icon } from "@douyinfe/semi-ui";
|
||||
import React from 'react';
|
||||
import { Icon } from '@douyinfe/semi-ui';
|
||||
|
||||
export const IconOverview: React.FC<{ style?: React.CSSProperties }> = ({
|
||||
style = {},
|
||||
}) => {
|
||||
export const IconOverview: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
|
||||
return (
|
||||
<Icon
|
||||
style={style}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { Icon } from "@douyinfe/semi-ui";
|
||||
import { Icon } from '@douyinfe/semi-ui';
|
||||
|
||||
export const IconRight: React.FC<{ style?: React.CSSProperties }> = ({
|
||||
style = {},
|
||||
}) => {
|
||||
export const IconRight: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
|
||||
return (
|
||||
<Icon
|
||||
style={style}
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import React from "react";
|
||||
import { Icon } from "@douyinfe/semi-ui";
|
||||
import React from 'react';
|
||||
import { Icon } from '@douyinfe/semi-ui';
|
||||
|
||||
export const IconSearch: React.FC<{ style?: React.CSSProperties }> = ({
|
||||
style = {},
|
||||
}) => {
|
||||
export const IconSearch: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
|
||||
return (
|
||||
<Icon
|
||||
style={style}
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import React from "react";
|
||||
import { Icon } from "@douyinfe/semi-ui";
|
||||
import React from 'react';
|
||||
import { Icon } from '@douyinfe/semi-ui';
|
||||
|
||||
export const IconSetting: React.FC<{ style?: React.CSSProperties }> = ({
|
||||
style = {},
|
||||
}) => {
|
||||
export const IconSetting: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
|
||||
return (
|
||||
<Icon
|
||||
style={style}
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import React from "react";
|
||||
import { Icon } from "@douyinfe/semi-ui";
|
||||
import React from 'react';
|
||||
import { Icon } from '@douyinfe/semi-ui';
|
||||
|
||||
export const IconShare: React.FC<{ style?: React.CSSProperties }> = ({
|
||||
style = {},
|
||||
}) => {
|
||||
export const IconShare: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
|
||||
return (
|
||||
<Icon
|
||||
style={style}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { Icon } from "@douyinfe/semi-ui";
|
||||
import { Icon } from '@douyinfe/semi-ui';
|
||||
|
||||
export const IconSplitCell: React.FC<{ style?: React.CSSProperties }> = ({
|
||||
style = {},
|
||||
}) => {
|
||||
export const IconSplitCell: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
|
||||
return (
|
||||
<Icon
|
||||
style={style}
|
||||
|
|
|
@ -1,18 +1,12 @@
|
|||
import { Icon } from "@douyinfe/semi-ui";
|
||||
import { Icon } from '@douyinfe/semi-ui';
|
||||
|
||||
export const IconStatus: React.FC<{ style?: React.CSSProperties }> = ({
|
||||
style = {},
|
||||
}) => {
|
||||
export const IconStatus: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
|
||||
return (
|
||||
<Icon
|
||||
style={style}
|
||||
svg={
|
||||
<svg width="16" height="16" viewBox="0 0 256 256" role="presentation">
|
||||
<g
|
||||
transform="rotate(-45 100.071 47.645)"
|
||||
fill="currentColor"
|
||||
fill-rule="evenodd"
|
||||
>
|
||||
<g transform="rotate(-45 100.071 47.645)" fill="currentColor" fill-rule="evenodd">
|
||||
<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"
|
||||
fill-rule="nonzero"
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { Icon } from "@douyinfe/semi-ui";
|
||||
import { Icon } from '@douyinfe/semi-ui';
|
||||
|
||||
export const IconTable: React.FC<{ style?: React.CSSProperties }> = ({
|
||||
style = {},
|
||||
}) => {
|
||||
export const IconTable: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
|
||||
return (
|
||||
<Icon
|
||||
style={style}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { Icon } from "@douyinfe/semi-ui";
|
||||
import { Icon } from '@douyinfe/semi-ui';
|
||||
|
||||
export const IconTask: React.FC<{ style?: React.CSSProperties }> = ({
|
||||
style = {},
|
||||
}) => {
|
||||
export const IconTask: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
|
||||
return (
|
||||
<Icon
|
||||
style={style}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { Icon } from "@douyinfe/semi-ui";
|
||||
import { Icon } from '@douyinfe/semi-ui';
|
||||
|
||||
export const IconZoomIn: React.FC<{ style?: React.CSSProperties }> = ({
|
||||
style = {},
|
||||
}) => {
|
||||
export const IconZoomIn: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
|
||||
return (
|
||||
<Icon
|
||||
style={style}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { Icon } from "@douyinfe/semi-ui";
|
||||
import { Icon } from '@douyinfe/semi-ui';
|
||||
|
||||
export const IconZoomOut: React.FC<{ style?: React.CSSProperties }> = ({
|
||||
style = {},
|
||||
}) => {
|
||||
export const IconZoomOut: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
|
||||
return (
|
||||
<Icon
|
||||
style={style}
|
||||
|
|
|
@ -1,36 +1,36 @@
|
|||
export * from "./IconDocument";
|
||||
export * from "./IconDocumentFill";
|
||||
export * from "./IconMessage";
|
||||
export * from "./IconOverview";
|
||||
export * from "./IconSetting";
|
||||
export * from "./IconShare";
|
||||
export * from "./IconLeft";
|
||||
export * from "./IconRight";
|
||||
export * from "./IconFull";
|
||||
export * from "./IconHalf";
|
||||
export * from "./IconCenter";
|
||||
export * from "./IconFont";
|
||||
export * from "./IconTask";
|
||||
export * from "./IconLink";
|
||||
export * from "./IconClear";
|
||||
export * from "./IconImage";
|
||||
export * from "./IconMind";
|
||||
export * from "./IconZoomIn";
|
||||
export * from "./IconZoomOut";
|
||||
export * from "./IconTable";
|
||||
export * from "./IconCodeBlock";
|
||||
export * from "./IconStatus";
|
||||
export * from "./IconInfo";
|
||||
export * from "./IconEmoji";
|
||||
export * from "./IconAddColumnBefore";
|
||||
export * from "./IconAddColumnAfter";
|
||||
export * from "./IconDeleteColumn";
|
||||
export * from "./IconAddRowBefore";
|
||||
export * from "./IconAddRowAfter";
|
||||
export * from "./IconDeleteRow";
|
||||
export * from "./IconDeleteTable";
|
||||
export * from "./IconMergeCell";
|
||||
export * from "./IconSplitCell";
|
||||
export * from "./IconAttachment";
|
||||
export * from "./IconMath";
|
||||
export * from "./IconSearch";
|
||||
export * from './IconDocument';
|
||||
export * from './IconDocumentFill';
|
||||
export * from './IconMessage';
|
||||
export * from './IconOverview';
|
||||
export * from './IconSetting';
|
||||
export * from './IconShare';
|
||||
export * from './IconLeft';
|
||||
export * from './IconRight';
|
||||
export * from './IconFull';
|
||||
export * from './IconHalf';
|
||||
export * from './IconCenter';
|
||||
export * from './IconFont';
|
||||
export * from './IconTask';
|
||||
export * from './IconLink';
|
||||
export * from './IconClear';
|
||||
export * from './IconImage';
|
||||
export * from './IconMind';
|
||||
export * from './IconZoomIn';
|
||||
export * from './IconZoomOut';
|
||||
export * from './IconTable';
|
||||
export * from './IconCodeBlock';
|
||||
export * from './IconStatus';
|
||||
export * from './IconInfo';
|
||||
export * from './IconEmoji';
|
||||
export * from './IconAddColumnBefore';
|
||||
export * from './IconAddColumnAfter';
|
||||
export * from './IconDeleteColumn';
|
||||
export * from './IconAddRowBefore';
|
||||
export * from './IconAddRowAfter';
|
||||
export * from './IconDeleteRow';
|
||||
export * from './IconDeleteTable';
|
||||
export * from './IconMergeCell';
|
||||
export * from './IconSplitCell';
|
||||
export * from './IconAttachment';
|
||||
export * from './IconMath';
|
||||
export * from './IconSearch';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useRef, useState, useEffect } from "react";
|
||||
import distanceInWords from "date-fns/formatDistance";
|
||||
import dateFormat from "date-fns/format";
|
||||
import zh from "date-fns/locale/zh-CN";
|
||||
import React, { useRef, useState, useEffect } from 'react';
|
||||
import distanceInWords from 'date-fns/formatDistance';
|
||||
import dateFormat from 'date-fns/format';
|
||||
import zh from 'date-fns/locale/zh-CN';
|
||||
|
||||
let callbacks: Array<() => void> = [];
|
||||
|
||||
|
@ -30,18 +30,14 @@ const getTimeago = (date: number | string | Date) => {
|
|||
});
|
||||
|
||||
content = content
|
||||
.replace("about", "")
|
||||
.replace("less than a minute ago", "just now")
|
||||
.replace("minute", "min");
|
||||
.replace('about', '')
|
||||
.replace('less than a minute ago', 'just now')
|
||||
.replace('minute', 'min');
|
||||
|
||||
return content;
|
||||
};
|
||||
|
||||
export const LocaleTime: React.FC<Props> = ({
|
||||
date,
|
||||
timeago,
|
||||
format = "yyyy-MM-dd HH:mm:ss",
|
||||
}) => {
|
||||
export const LocaleTime: React.FC<Props> = ({ date, timeago, format = 'yyyy-MM-dd HH:mm:ss' }) => {
|
||||
const [_, setMinutesMounted] = useState(0); // eslint-disable-line no-unused-vars
|
||||
const callback = useRef<() => void>();
|
||||
|
||||
|
@ -59,7 +55,5 @@ export const LocaleTime: React.FC<Props> = ({
|
|||
|
||||
const formated = dateFormat(new Date(date), format);
|
||||
|
||||
return (
|
||||
<time dateTime={formated}>{timeago ? getTimeago(date) : formated}</time>
|
||||
);
|
||||
return <time dateTime={formated}>{timeago ? getTimeago(date) : formated}</time>;
|
||||
};
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import Link from "next/link";
|
||||
import { Typography } from "@douyinfe/semi-ui";
|
||||
import styles from "./index.module.scss";
|
||||
import Link from 'next/link';
|
||||
import { Typography } from '@douyinfe/semi-ui';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
export const LogoImage = () => {
|
||||
return (
|
||||
<Link href={"/"} as={"/"}>
|
||||
<Link href={'/'} as={'/'}>
|
||||
<a style={{ width: 36, height: 36 }}>
|
||||
<svg
|
||||
viewBox="0 0 1024 1024"
|
||||
|
@ -39,7 +39,7 @@ export const LogoImage = () => {
|
|||
|
||||
export const LogoText = () => {
|
||||
return (
|
||||
<Link href={"/"} as={"/"}>
|
||||
<Link href={'/'} as={'/'}>
|
||||
<a className={styles.wrap}>
|
||||
<Text>云策文档</Text>
|
||||
</a>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Skeleton } from "@douyinfe/semi-ui";
|
||||
import { Skeleton } from '@douyinfe/semi-ui';
|
||||
|
||||
export const Placeholder = () => {
|
||||
return (
|
||||
|
@ -6,9 +6,7 @@ export const Placeholder = () => {
|
|||
placeholder={
|
||||
<>
|
||||
{Array.from({ length: 6 }).fill(
|
||||
<Skeleton.Title
|
||||
style={{ width: "100%", marginBottom: 12, marginTop: 12 }}
|
||||
/>
|
||||
<Skeleton.Title style={{ width: '100%', marginBottom: 12, marginTop: 12 }} />
|
||||
)}
|
||||
</>
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useEffect } from "react";
|
||||
import Link from "next/link";
|
||||
import React, { useEffect } from 'react';
|
||||
import Link from 'next/link';
|
||||
import {
|
||||
Typography,
|
||||
Dropdown,
|
||||
|
@ -9,18 +9,14 @@ import {
|
|||
TabPane,
|
||||
Pagination,
|
||||
Notification,
|
||||
} from "@douyinfe/semi-ui";
|
||||
import { IconMessage } from "components/icons/IconMessage";
|
||||
import {
|
||||
useAllMessages,
|
||||
useReadMessages,
|
||||
useUnreadMessages,
|
||||
} from "data/message";
|
||||
import { EmptyBoxIllustration } from "illustrations/empty-box";
|
||||
import { DataRender } from "components/data-render";
|
||||
import { Empty } from "components/empty";
|
||||
import { Placeholder } from "./Placeholder";
|
||||
import styles from "./index.module.scss";
|
||||
} from '@douyinfe/semi-ui';
|
||||
import { IconMessage } from 'components/icons/IconMessage';
|
||||
import { useAllMessages, useReadMessages, useUnreadMessages } from 'data/message';
|
||||
import { EmptyBoxIllustration } from 'illustrations/empty-box';
|
||||
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 PAGE_SIZE = 6;
|
||||
|
@ -49,7 +45,7 @@ const MessagesRender = ({
|
|||
return (
|
||||
<div
|
||||
className={styles.itemsWrap}
|
||||
style={{ margin: "8px -16px" }}
|
||||
style={{ margin: '8px -16px' }}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
@ -59,10 +55,7 @@ const MessagesRender = ({
|
|||
<>
|
||||
{messages.map((msg) => {
|
||||
return (
|
||||
<div
|
||||
className={styles.itemWrap}
|
||||
onClick={() => handleRead(msg.id)}
|
||||
>
|
||||
<div className={styles.itemWrap} onClick={() => handleRead(msg.id)}>
|
||||
<Link href={msg.url}>
|
||||
<a className={styles.item}>
|
||||
<div className={styles.leftWrap}>
|
||||
|
@ -89,17 +82,14 @@ const MessagesRender = ({
|
|||
total={total}
|
||||
currentPage={page}
|
||||
pageSize={PAGE_SIZE}
|
||||
style={{ textAlign: "center" }}
|
||||
style={{ textAlign: 'center' }}
|
||||
onPageChange={onPageChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Empty
|
||||
illustration={<EmptyBoxIllustration />}
|
||||
message="暂无消息"
|
||||
/>
|
||||
<Empty illustration={<EmptyBoxIllustration />} message="暂无消息" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
@ -146,7 +136,7 @@ export const Message = () => {
|
|||
const msg = unreadMsgs.data[0];
|
||||
|
||||
Notification.info({
|
||||
title: "消息通知",
|
||||
title: '消息通知',
|
||||
content: (
|
||||
<Link href={msg.url}>
|
||||
<a className={styles.item}>
|
||||
|
@ -178,17 +168,13 @@ export const Message = () => {
|
|||
position="bottomRight"
|
||||
trigger="click"
|
||||
content={
|
||||
<div style={{ width: 300, padding: "16px 16px 0" }}>
|
||||
<div style={{ width: 300, padding: '16px 16px 0' }}>
|
||||
<Tabs
|
||||
type="line"
|
||||
size="small"
|
||||
tabBarExtraContent={
|
||||
unreadMsgs && unreadMsgs.total > 0 ? (
|
||||
<Text
|
||||
type="quaternary"
|
||||
onClick={clearAll}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
<Text type="quaternary" onClick={clearAll} style={{ cursor: 'pointer' }}>
|
||||
全部已读
|
||||
</Text>
|
||||
) : null
|
||||
|
|
|
@ -1 +1 @@
|
|||
export * from "./resizeable";
|
||||
export * from './resizeable';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useRef, useEffect } from "react";
|
||||
import { useClickOutside } from "hooks/use-click-outside";
|
||||
import interact from "interactjs";
|
||||
import styles from "./style.module.scss";
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
import { useClickOutside } from 'hooks/use-click-outside';
|
||||
import interact from 'interactjs';
|
||||
import styles from './style.module.scss';
|
||||
|
||||
interface IProps {
|
||||
width: number;
|
||||
|
@ -12,12 +12,7 @@ interface IProps {
|
|||
const MIN_WIDTH = 50;
|
||||
const MIN_HEIGHT = 50;
|
||||
|
||||
export const Resizeable: React.FC<IProps> = ({
|
||||
width,
|
||||
height,
|
||||
onChange,
|
||||
children,
|
||||
}) => {
|
||||
export const Resizeable: React.FC<IProps> = ({ width, height, onChange, children }) => {
|
||||
const $container = useRef<HTMLDivElement>(null);
|
||||
const $topLeft = useRef<HTMLDivElement>(null);
|
||||
const $topRight = useRef<HTMLDivElement>(null);
|
||||
|
@ -67,24 +62,24 @@ export const Resizeable: React.FC<IProps> = ({
|
|||
style={{ width, height }}
|
||||
>
|
||||
<span
|
||||
className={styles.resizer + " " + styles.topLeft}
|
||||
className={styles.resizer + ' ' + styles.topLeft}
|
||||
ref={$topLeft}
|
||||
data-type={"topLeft"}
|
||||
data-type={'topLeft'}
|
||||
></span>
|
||||
<span
|
||||
className={styles.resizer + " " + styles.topRight}
|
||||
className={styles.resizer + ' ' + styles.topRight}
|
||||
ref={$topRight}
|
||||
data-type={"topRight"}
|
||||
data-type={'topRight'}
|
||||
></span>
|
||||
<span
|
||||
className={styles.resizer + " " + styles.bottomLeft}
|
||||
className={styles.resizer + ' ' + styles.bottomLeft}
|
||||
ref={$bottomLeft}
|
||||
data-type={"bottomLeft"}
|
||||
data-type={'bottomLeft'}
|
||||
></span>
|
||||
<span
|
||||
className={styles.resizer + " " + styles.bottomRight}
|
||||
className={styles.resizer + ' ' + styles.bottomRight}
|
||||
ref={$bottomRight}
|
||||
data-type={"bottomRight"}
|
||||
data-type={'bottomRight'}
|
||||
></span>
|
||||
{children}
|
||||
</div>
|
||||
|
|
|
@ -38,10 +38,7 @@ const List: React.FC<{ data: IDocument[] }> = ({ data }) => {
|
|||
<div className={styles.leftWrap}>
|
||||
<IconDocumentFill style={{ marginRight: 12 }} />
|
||||
<div>
|
||||
<Text
|
||||
ellipsis={{ showTooltip: true }}
|
||||
style={{ width: 180 }}
|
||||
>
|
||||
<Text ellipsis={{ showTooltip: true }} style={{ width: 180 }}>
|
||||
{doc.title}
|
||||
</Text>
|
||||
|
||||
|
@ -100,12 +97,7 @@ export const Search = () => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
type="tertiary"
|
||||
theme="borderless"
|
||||
icon={<IconSearch />}
|
||||
onClick={toggleVisible}
|
||||
/>
|
||||
<Button type="tertiary" theme="borderless" icon={<IconSearch />} onClick={toggleVisible} />
|
||||
<Modal
|
||||
visible={visible}
|
||||
title="文档搜索"
|
||||
|
@ -126,12 +118,7 @@ export const Search = () => {
|
|||
setKeyword(val);
|
||||
}}
|
||||
onEnterPress={search}
|
||||
suffix={
|
||||
<SemiIconSearch
|
||||
onClick={search}
|
||||
style={{ cursor: 'pointer' }}
|
||||
/>
|
||||
}
|
||||
suffix={<SemiIconSearch onClick={search} style={{ cursor: 'pointer' }} />}
|
||||
showClear
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from "react";
|
||||
import { Helmet } from "react-helmet";
|
||||
import React from 'react';
|
||||
import { Helmet } from 'react-helmet';
|
||||
|
||||
interface IProps {
|
||||
title: string;
|
||||
|
|
|
@ -1,22 +1,14 @@
|
|||
import type { ITemplate } from "@think/domains";
|
||||
import { useCallback } from "react";
|
||||
import cls from "classnames";
|
||||
import Router from "next/router";
|
||||
import {
|
||||
Button,
|
||||
Space,
|
||||
Typography,
|
||||
Tooltip,
|
||||
Avatar,
|
||||
Skeleton,
|
||||
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";
|
||||
import type { ITemplate } from '@think/domains';
|
||||
import { useCallback } from 'react';
|
||||
import cls from 'classnames';
|
||||
import Router from 'next/router';
|
||||
import { Button, Space, Typography, Tooltip, Avatar, Skeleton, 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;
|
||||
|
||||
|
@ -31,7 +23,7 @@ export interface IProps {
|
|||
export const TemplateCard: React.FC<IProps> = ({
|
||||
template,
|
||||
onClick,
|
||||
getClassNames = (id) => "",
|
||||
getClassNames = (id) => '',
|
||||
onOpenPreview,
|
||||
onClosePreview,
|
||||
}) => {
|
||||
|
@ -46,10 +38,10 @@ export const TemplateCard: React.FC<IProps> = ({
|
|||
<>
|
||||
<Modal
|
||||
title="模板预览"
|
||||
width={"calc(100vh - 120px)"}
|
||||
height={"calc(100vh - 120px)"}
|
||||
width={'calc(100vh - 120px)'}
|
||||
height={'calc(100vh - 120px)'}
|
||||
bodyStyle={{
|
||||
overflow: "auto",
|
||||
overflow: 'auto',
|
||||
}}
|
||||
visible={visible}
|
||||
onCancel={() => {
|
||||
|
@ -100,7 +92,7 @@ export const TemplateCard: React.FC<IProps> = ({
|
|||
</main>
|
||||
<footer>
|
||||
<Text type="tertiary" size="small">
|
||||
<div style={{ display: "flex" }}>
|
||||
<div style={{ display: 'flex' }}>
|
||||
已使用
|
||||
{template.usageAmount}次
|
||||
</div>
|
||||
|
@ -118,11 +110,7 @@ export const TemplateCard: React.FC<IProps> = ({
|
|||
预览
|
||||
</Button>
|
||||
{onClick && (
|
||||
<Button
|
||||
type="primary"
|
||||
theme="solid"
|
||||
onClick={() => onClick && onClick(template.id)}
|
||||
>
|
||||
<Button type="primary" theme="solid" onClick={() => onClick && onClick(template.id)}>
|
||||
使用
|
||||
</Button>
|
||||
)}
|
||||
|
@ -156,7 +144,7 @@ export const TemplateCardPlaceholder = () => {
|
|||
</main>
|
||||
<footer>
|
||||
<Text type="tertiary" size="small">
|
||||
<div style={{ display: "flex" }}>
|
||||
<div style={{ display: 'flex' }}>
|
||||
更新时间:
|
||||
<Skeleton.Paragraph rows={1} style={{ width: 100 }} />
|
||||
</div>
|
||||
|
@ -166,36 +154,33 @@ export const TemplateCardPlaceholder = () => {
|
|||
);
|
||||
};
|
||||
|
||||
export const TemplateCardEmpty = ({
|
||||
getClassNames = () => "",
|
||||
onClick = () => {},
|
||||
}) => {
|
||||
export const TemplateCardEmpty = ({ getClassNames = () => '', onClick = () => {} }) => {
|
||||
return (
|
||||
<div className={cls(styles.cardWrap, getClassNames())} onClick={onClick}>
|
||||
<div
|
||||
style={{
|
||||
height: 131,
|
||||
position: "relative",
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: "50%",
|
||||
top: "50%",
|
||||
position: 'absolute',
|
||||
left: '50%',
|
||||
top: '50%',
|
||||
transform: `translate(-50%, -50%)`,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Text link style={{ textAlign: "center" }}>
|
||||
<Text link style={{ textAlign: 'center' }}>
|
||||
<IconPlus
|
||||
style={{
|
||||
width: 36,
|
||||
height: 36,
|
||||
fontSize: 36,
|
||||
margin: "0 auto 12px",
|
||||
margin: '0 auto 12px',
|
||||
}}
|
||||
/>
|
||||
</Text>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useMemo, useCallback, useState, useEffect } from "react";
|
||||
import Router from "next/router";
|
||||
import cls from "classnames";
|
||||
import { useEditor, EditorContent } from "@tiptap/react";
|
||||
import React, { useMemo, useCallback, useState, useEffect } from 'react';
|
||||
import Router from 'next/router';
|
||||
import cls from 'classnames';
|
||||
import { useEditor, EditorContent } from '@tiptap/react';
|
||||
import {
|
||||
Button,
|
||||
Nav,
|
||||
|
@ -14,10 +14,10 @@ import {
|
|||
Popover,
|
||||
Popconfirm,
|
||||
BackTop,
|
||||
} from "@douyinfe/semi-ui";
|
||||
import { IconChevronLeft, IconArticle } from "@douyinfe/semi-icons";
|
||||
import { ILoginUser, ITemplate } from "@think/domains";
|
||||
import { Theme } from "components/theme";
|
||||
} from '@douyinfe/semi-ui';
|
||||
import { IconChevronLeft, IconArticle } from '@douyinfe/semi-icons';
|
||||
import { ILoginUser, ITemplate } from '@think/domains';
|
||||
import { Theme } from 'components/theme';
|
||||
import {
|
||||
DEFAULT_EXTENSION,
|
||||
DocumentWithTitle,
|
||||
|
@ -25,13 +25,13 @@ import {
|
|||
getProvider,
|
||||
MenuBar,
|
||||
Toc,
|
||||
} from "components/tiptap";
|
||||
import { DataRender } from "components/data-render";
|
||||
import { User } from "components/user";
|
||||
import { DocumentStyle } from "components/document/style";
|
||||
import { useDocumentStyle } from "hooks/useDocumentStyle";
|
||||
import { safeJSONParse } from "helpers/json";
|
||||
import styles from "./index.module.scss";
|
||||
} from 'components/tiptap';
|
||||
import { DataRender } from 'components/data-render';
|
||||
import { User } from 'components/user';
|
||||
import { DocumentStyle } from 'components/document/style';
|
||||
import { useDocumentStyle } from 'hooks/useDocumentStyle';
|
||||
import { safeJSONParse } from 'helpers/json';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
|
@ -58,27 +58,21 @@ export const Editor: React.FC<IProps> = ({
|
|||
return getProvider({
|
||||
targetId: data.id,
|
||||
token: user.token,
|
||||
cacheType: "READER",
|
||||
cacheType: 'READER',
|
||||
user,
|
||||
docType: "template",
|
||||
docType: 'template',
|
||||
});
|
||||
}, [data, user.token]);
|
||||
const editor = useEditor({
|
||||
editable: true,
|
||||
extensions: [
|
||||
...DEFAULT_EXTENSION,
|
||||
DocumentWithTitle,
|
||||
getCollaborationExtension(provider),
|
||||
],
|
||||
extensions: [...DEFAULT_EXTENSION, DocumentWithTitle, getCollaborationExtension(provider)],
|
||||
content: safeJSONParse(data && data.content),
|
||||
});
|
||||
|
||||
const [isPublic, setPublic] = useState(false);
|
||||
const { width, fontSize } = useDocumentStyle();
|
||||
const editorWrapClassNames = useMemo(() => {
|
||||
return width === "standardWidth"
|
||||
? styles.isStandardWidth
|
||||
: styles.isFullWidth;
|
||||
return width === 'standardWidth' ? styles.isStandardWidth : styles.isFullWidth;
|
||||
}, [width]);
|
||||
|
||||
const goback = useCallback(() => {
|
||||
|
@ -100,7 +94,7 @@ export const Editor: React.FC<IProps> = ({
|
|||
<div className={styles.wrap}>
|
||||
<header>
|
||||
<Nav
|
||||
style={{ overflow: "auto" }}
|
||||
style={{ overflow: 'auto' }}
|
||||
mode="horizontal"
|
||||
header={
|
||||
<DataRender
|
||||
|
@ -109,9 +103,7 @@ export const Editor: React.FC<IProps> = ({
|
|||
loadingContent={
|
||||
<Skeleton
|
||||
active
|
||||
placeholder={
|
||||
<Skeleton.Title style={{ width: 80, marginBottom: 8 }} />
|
||||
}
|
||||
placeholder={<Skeleton.Title style={{ width: 80, marginBottom: 8 }} />}
|
||||
loading={true}
|
||||
/>
|
||||
}
|
||||
|
@ -124,11 +116,7 @@ export const Editor: React.FC<IProps> = ({
|
|||
style={{ marginRight: 16 }}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Text
|
||||
strong
|
||||
ellipsis={{ showTooltip: true }}
|
||||
style={{ width: 120 }}
|
||||
>
|
||||
<Text strong ellipsis={{ showTooltip: true }} style={{ width: 120 }}>
|
||||
{data.title}
|
||||
</Text>
|
||||
</>
|
||||
|
@ -137,25 +125,11 @@ export const Editor: React.FC<IProps> = ({
|
|||
}
|
||||
footer={
|
||||
<Space>
|
||||
<Popover
|
||||
key="style"
|
||||
zIndex={1061}
|
||||
position="bottomLeft"
|
||||
content={<DocumentStyle />}
|
||||
>
|
||||
<Button
|
||||
icon={<IconArticle />}
|
||||
theme="borderless"
|
||||
type="tertiary"
|
||||
/>
|
||||
<Popover key="style" zIndex={1061} position="bottomLeft" content={<DocumentStyle />}>
|
||||
<Button icon={<IconArticle />} theme="borderless" type="tertiary" />
|
||||
</Popover>
|
||||
<Tooltip
|
||||
position="bottom"
|
||||
content={isPublic ? "公开模板" : "个人模板"}
|
||||
>
|
||||
<Switch
|
||||
onChange={(v) => updateTemplate({ isPublic: v })}
|
||||
></Switch>
|
||||
<Tooltip position="bottom" content={isPublic ? '公开模板' : '个人模板'}>
|
||||
<Switch onChange={(v) => updateTemplate({ isPublic: v })}></Switch>
|
||||
</Tooltip>
|
||||
<Popconfirm
|
||||
title="删除模板"
|
||||
|
@ -194,11 +168,7 @@ export const Editor: React.FC<IProps> = ({
|
|||
>
|
||||
<EditorContent editor={editor} />
|
||||
</div>
|
||||
<BackTop
|
||||
target={() =>
|
||||
document.querySelector("#js-template-editor-container")
|
||||
}
|
||||
/>
|
||||
<BackTop target={() => document.querySelector('#js-template-editor-container')} />
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import React from "react";
|
||||
import { Spin } from "@douyinfe/semi-ui";
|
||||
import { useUser } from "data/user";
|
||||
import { Seo } from "components/seo";
|
||||
import { DataRender } from "components/data-render";
|
||||
import { useTemplate } from "data/template";
|
||||
import { Editor } from "./editor";
|
||||
import React from 'react';
|
||||
import { Spin } from '@douyinfe/semi-ui';
|
||||
import { useUser } from 'data/user';
|
||||
import { Seo } from 'components/seo';
|
||||
import { DataRender } from 'components/data-render';
|
||||
import { useTemplate } from 'data/template';
|
||||
import { Editor } from './editor';
|
||||
|
||||
interface IProps {
|
||||
templateId: string;
|
||||
|
@ -12,8 +12,7 @@ interface IProps {
|
|||
|
||||
export const TemplateEditor: React.FC<IProps> = ({ templateId }) => {
|
||||
const { user } = useUser();
|
||||
const { data, loading, error, updateTemplate, deleteTemplate } =
|
||||
useTemplate(templateId);
|
||||
const { data, loading, error, updateTemplate, deleteTemplate } = useTemplate(templateId);
|
||||
|
||||
return (
|
||||
<DataRender
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import React, { useState, useMemo } from "react";
|
||||
import { List, Pagination } from "@douyinfe/semi-ui";
|
||||
import { DataRender } from "components/data-render";
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { List, Pagination } from '@douyinfe/semi-ui';
|
||||
import { DataRender } from 'components/data-render';
|
||||
import {
|
||||
IProps as ITemplateCardProps,
|
||||
TemplateCardPlaceholder,
|
||||
TemplateCard,
|
||||
} from "components/template/card";
|
||||
import { Empty } from "components/empty";
|
||||
} from 'components/template/card';
|
||||
import { Empty } from 'components/empty';
|
||||
|
||||
const grid = {
|
||||
gutter: 16,
|
||||
|
@ -17,7 +17,7 @@ const grid = {
|
|||
xl: 8,
|
||||
};
|
||||
|
||||
interface IProps extends Omit<ITemplateCardProps, "template"> {
|
||||
interface IProps extends Omit<ITemplateCardProps, 'template'> {
|
||||
// TODO: 修复类型
|
||||
hook: any;
|
||||
firstListItem?: React.ReactNode;
|
||||
|
@ -81,15 +81,15 @@ export const TemplateList: React.FC<IProps> = ({
|
|||
</List.Item>
|
||||
);
|
||||
}}
|
||||
emptyContent={<Empty message={"暂无模板"} />}
|
||||
emptyContent={<Empty message={'暂无模板'} />}
|
||||
></List>
|
||||
{data.data.length > pageSize ? (
|
||||
<Pagination
|
||||
size="small"
|
||||
style={{
|
||||
width: "100%",
|
||||
flexBasis: "100%",
|
||||
justifyContent: "center",
|
||||
width: '100%',
|
||||
flexBasis: '100%',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
pageSize={pageSize}
|
||||
total={data.data.length}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import React, { useMemo } from "react";
|
||||
import cls from "classnames";
|
||||
import { useEditor, EditorContent } from "@tiptap/react";
|
||||
import { Layout, Spin, Typography } from "@douyinfe/semi-ui";
|
||||
import { IUser, ITemplate } from "@think/domains";
|
||||
import { DEFAULT_EXTENSION, DocumentWithTitle } from "components/tiptap";
|
||||
import { DataRender } from "components/data-render";
|
||||
import { useDocumentStyle } from "hooks/useDocumentStyle";
|
||||
import { safeJSONParse } from "helpers/json";
|
||||
import styles from "./index.module.scss";
|
||||
import React, { useMemo } from 'react';
|
||||
import cls from 'classnames';
|
||||
import { useEditor, EditorContent } from '@tiptap/react';
|
||||
import { Layout, Spin, Typography } from '@douyinfe/semi-ui';
|
||||
import { IUser, ITemplate } from '@think/domains';
|
||||
import { DEFAULT_EXTENSION, DocumentWithTitle } from 'components/tiptap';
|
||||
import { DataRender } from 'components/data-render';
|
||||
import { useDocumentStyle } from 'hooks/useDocumentStyle';
|
||||
import { safeJSONParse } from 'helpers/json';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const { Content } = Layout;
|
||||
const { Title } = Typography;
|
||||
|
@ -27,7 +27,7 @@ export const Editor: React.FC<IProps> = ({ user, data, loading, error }) => {
|
|||
|
||||
if (json && json.content) {
|
||||
json = {
|
||||
type: "doc",
|
||||
type: 'doc',
|
||||
content: json.content.slice(1),
|
||||
};
|
||||
}
|
||||
|
@ -40,9 +40,7 @@ export const Editor: React.FC<IProps> = ({ user, data, loading, error }) => {
|
|||
|
||||
const { width, fontSize } = useDocumentStyle();
|
||||
const editorWrapClassNames = useMemo(() => {
|
||||
return width === "standardWidth"
|
||||
? styles.isStandardWidth
|
||||
: styles.isFullWidth;
|
||||
return width === 'standardWidth' ? styles.isStandardWidth : styles.isFullWidth;
|
||||
}, [width]);
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import React from "react";
|
||||
import { Spin } from "@douyinfe/semi-ui";
|
||||
import { useUser } from "data/user";
|
||||
import { Seo } from "components/seo";
|
||||
import { DataRender } from "components/data-render";
|
||||
import { useTemplate } from "data/template";
|
||||
import { Editor } from "./editor";
|
||||
import React from 'react';
|
||||
import { Spin } from '@douyinfe/semi-ui';
|
||||
import { useUser } from 'data/user';
|
||||
import { Seo } from 'components/seo';
|
||||
import { DataRender } from 'components/data-render';
|
||||
import { useTemplate } from 'data/template';
|
||||
import { Editor } from './editor';
|
||||
|
||||
interface IProps {
|
||||
templateId: string;
|
||||
|
|
|
@ -1,20 +1,16 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { Button, Tooltip } from "@douyinfe/semi-ui";
|
||||
import { IconSun, IconMoon } from "@douyinfe/semi-icons";
|
||||
import { useTheme } from "hooks/useTheme";
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, Tooltip } from '@douyinfe/semi-ui';
|
||||
import { IconSun, IconMoon } from '@douyinfe/semi-icons';
|
||||
import { useTheme } from 'hooks/useTheme';
|
||||
|
||||
export const Theme = () => {
|
||||
const { theme, toggle } = useTheme();
|
||||
const Icon = theme === "dark" ? IconSun : IconMoon;
|
||||
const text = theme === "dark" ? "切换到亮色模式" : "切换到深色模式";
|
||||
const Icon = theme === 'dark' ? IconSun : IconMoon;
|
||||
const text = theme === 'dark' ? '切换到亮色模式' : '切换到深色模式';
|
||||
|
||||
return (
|
||||
<Tooltip content={text} position="bottom">
|
||||
<Button
|
||||
onClick={toggle}
|
||||
icon={<Icon style={{ fontSize: 20 }} />}
|
||||
theme="borderless"
|
||||
></Button>
|
||||
<Button onClick={toggle} icon={<Icon style={{ fontSize: 20 }} />} theme="borderless"></Button>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,57 +1,57 @@
|
|||
import { Document, TitledDocument, Title } from "./extensions/title";
|
||||
import Placeholder from "@tiptap/extension-placeholder";
|
||||
import Paragraph from "@tiptap/extension-paragraph";
|
||||
import Text from "@tiptap/extension-text";
|
||||
import Strike from "@tiptap/extension-strike";
|
||||
import Underline from "@tiptap/extension-underline";
|
||||
import TextStyle from "@tiptap/extension-text-style";
|
||||
import { Color } from "@tiptap/extension-color";
|
||||
import Blockquote from "@tiptap/extension-blockquote";
|
||||
import Bold from "@tiptap/extension-bold";
|
||||
import Code from "@tiptap/extension-code";
|
||||
import Highlight from "@tiptap/extension-highlight";
|
||||
import TextAlign from "@tiptap/extension-text-align";
|
||||
import Dropcursor from "@tiptap/extension-dropcursor";
|
||||
import Gapcursor from "@tiptap/extension-gapcursor";
|
||||
import HardBreak from "@tiptap/extension-hard-break";
|
||||
import Heading from "@tiptap/extension-heading";
|
||||
import Italic from "@tiptap/extension-italic";
|
||||
import OrderedList from "@tiptap/extension-ordered-list";
|
||||
import BulletList from "@tiptap/extension-bullet-list";
|
||||
import ListItem from "@tiptap/extension-list-item";
|
||||
import TaskList from "@tiptap/extension-task-list";
|
||||
import TaskItem from "@tiptap/extension-task-item";
|
||||
import { HorizontalRule } from "./extensions/horizontal-rule";
|
||||
import { BackgroundColor } from "./extensions/background-color";
|
||||
import { Link } from "./extensions/link";
|
||||
import { FontSize } from "./extensions/font-size";
|
||||
import { ColorHighlighter } from "./extensions/color-highlight";
|
||||
import { Indent } from "./extensions/indent";
|
||||
import { Div } from "./extensions/div";
|
||||
import { Banner } from "./extensions/banner";
|
||||
import { CodeBlock } from "./extensions/code-block";
|
||||
import { Iframe } from "./extensions/iframe";
|
||||
import { Mind } from "./extensions/mind";
|
||||
import { Image } from "./extensions/image";
|
||||
import { Status } from "./extensions/status";
|
||||
import { Paste } from "./extensions/paste";
|
||||
import { Table, TableRow, TableCell, TableHeader } from "./extensions/table";
|
||||
import { Toc } from "./extensions/toc";
|
||||
import { TrailingNode } from "./extensions/trailing-node";
|
||||
import { Attachment } from "./extensions/attachment";
|
||||
import { Katex } from "./extensions/katex";
|
||||
import { DocumentReference } from "./extensions/documents/reference";
|
||||
import { DocumentChildren } from "./extensions/documents/children";
|
||||
import { Document, TitledDocument, Title } from './extensions/title';
|
||||
import Placeholder from '@tiptap/extension-placeholder';
|
||||
import Paragraph from '@tiptap/extension-paragraph';
|
||||
import Text from '@tiptap/extension-text';
|
||||
import Strike from '@tiptap/extension-strike';
|
||||
import Underline from '@tiptap/extension-underline';
|
||||
import TextStyle from '@tiptap/extension-text-style';
|
||||
import { Color } from '@tiptap/extension-color';
|
||||
import Blockquote from '@tiptap/extension-blockquote';
|
||||
import Bold from '@tiptap/extension-bold';
|
||||
import Code from '@tiptap/extension-code';
|
||||
import Highlight from '@tiptap/extension-highlight';
|
||||
import TextAlign from '@tiptap/extension-text-align';
|
||||
import Dropcursor from '@tiptap/extension-dropcursor';
|
||||
import Gapcursor from '@tiptap/extension-gapcursor';
|
||||
import HardBreak from '@tiptap/extension-hard-break';
|
||||
import Heading from '@tiptap/extension-heading';
|
||||
import Italic from '@tiptap/extension-italic';
|
||||
import OrderedList from '@tiptap/extension-ordered-list';
|
||||
import BulletList from '@tiptap/extension-bullet-list';
|
||||
import ListItem from '@tiptap/extension-list-item';
|
||||
import TaskList from '@tiptap/extension-task-list';
|
||||
import TaskItem from '@tiptap/extension-task-item';
|
||||
import { HorizontalRule } from './extensions/horizontal-rule';
|
||||
import { BackgroundColor } from './extensions/background-color';
|
||||
import { Link } from './extensions/link';
|
||||
import { FontSize } from './extensions/font-size';
|
||||
import { ColorHighlighter } from './extensions/color-highlight';
|
||||
import { Indent } from './extensions/indent';
|
||||
import { Div } from './extensions/div';
|
||||
import { Banner } from './extensions/banner';
|
||||
import { CodeBlock } from './extensions/code-block';
|
||||
import { Iframe } from './extensions/iframe';
|
||||
import { Mind } from './extensions/mind';
|
||||
import { Image } from './extensions/image';
|
||||
import { Status } from './extensions/status';
|
||||
import { Paste } from './extensions/paste';
|
||||
import { Table, TableRow, TableCell, TableHeader } from './extensions/table';
|
||||
import { Toc } from './extensions/toc';
|
||||
import { TrailingNode } from './extensions/trailing-node';
|
||||
import { Attachment } from './extensions/attachment';
|
||||
import { Katex } from './extensions/katex';
|
||||
import { DocumentReference } from './extensions/documents/reference';
|
||||
import { DocumentChildren } from './extensions/documents/children';
|
||||
|
||||
export { Document, TitledDocument };
|
||||
|
||||
export const BaseExtension = [
|
||||
Placeholder.configure({
|
||||
placeholder: ({ node }) => {
|
||||
if (node.type.name === "title") {
|
||||
return "请输入标题";
|
||||
if (node.type.name === 'title') {
|
||||
return '请输入标题';
|
||||
}
|
||||
return "请输入内容";
|
||||
return '请输入内容';
|
||||
},
|
||||
showOnlyWhenEditable: true,
|
||||
}),
|
||||
|
@ -80,7 +80,7 @@ export const BaseExtension = [
|
|||
}),
|
||||
Highlight.configure({ multicolor: true }),
|
||||
TextAlign.configure({
|
||||
types: ["heading", "paragraph", "image"],
|
||||
types: ['heading', 'paragraph', 'image'],
|
||||
}),
|
||||
Link.configure({ openOnClick: false }),
|
||||
Blockquote,
|
||||
|
|
|
@ -1,12 +1,7 @@
|
|||
import {
|
||||
Editor,
|
||||
posToDOMRect,
|
||||
isTextSelection,
|
||||
isNodeSelection,
|
||||
} from "@tiptap/core";
|
||||
import { EditorState, Plugin, PluginKey } from "prosemirror-state";
|
||||
import { EditorView } from "prosemirror-view";
|
||||
import tippy, { Instance, Props } from "tippy.js";
|
||||
import { Editor, posToDOMRect, isTextSelection, 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 {
|
||||
pluginKey: PluginKey | string;
|
||||
|
@ -46,9 +41,9 @@ export class BubbleMenuView {
|
|||
|
||||
public renderContainerSelector?: string;
|
||||
|
||||
public matchRenderContainer?: BubbleMenuPluginProps["matchRenderContainer"];
|
||||
public matchRenderContainer?: BubbleMenuPluginProps['matchRenderContainer'];
|
||||
|
||||
public shouldShow: Exclude<BubbleMenuPluginProps["shouldShow"], null> = ({
|
||||
public shouldShow: Exclude<BubbleMenuPluginProps['shouldShow'], null> = ({
|
||||
view,
|
||||
state,
|
||||
from,
|
||||
|
@ -60,8 +55,7 @@ export class BubbleMenuView {
|
|||
// Sometime check for `empty` is not enough.
|
||||
// Doubleclick an empty paragraph returns a node size of 2.
|
||||
// So we check also for an empty text size.
|
||||
const isEmptyTextBlock =
|
||||
!doc.textBetween(from, to).length && isTextSelection(state.selection);
|
||||
const isEmptyTextBlock = !doc.textBetween(from, to).length && isTextSelection(state.selection);
|
||||
|
||||
if (!view.hasFocus() || empty || isEmptyTextBlock) {
|
||||
return false;
|
||||
|
@ -89,16 +83,16 @@ export class BubbleMenuView {
|
|||
this.shouldShow = shouldShow;
|
||||
}
|
||||
|
||||
this.element.addEventListener("mousedown", this.mousedownHandler, {
|
||||
this.element.addEventListener('mousedown', this.mousedownHandler, {
|
||||
capture: true,
|
||||
});
|
||||
this.view.dom.addEventListener("dragstart", this.dragstartHandler);
|
||||
this.editor.on("focus", this.focusHandler);
|
||||
this.editor.on("blur", this.blurHandler);
|
||||
this.view.dom.addEventListener('dragstart', this.dragstartHandler);
|
||||
this.editor.on('focus', this.focusHandler);
|
||||
this.editor.on('blur', this.blurHandler);
|
||||
this.tippyOptions = tippyOptions;
|
||||
// Detaches menu content from its current parent
|
||||
this.element.remove();
|
||||
this.element.style.visibility = "visible";
|
||||
this.element.style.visibility = 'visible';
|
||||
}
|
||||
|
||||
mousedownHandler = () => {
|
||||
|
@ -121,10 +115,7 @@ export class BubbleMenuView {
|
|||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
event?.relatedTarget &&
|
||||
this.element.parentNode?.contains(event.relatedTarget as Node)
|
||||
) {
|
||||
if (event?.relatedTarget && this.element.parentNode?.contains(event.relatedTarget as Node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -144,28 +135,24 @@ export class BubbleMenuView {
|
|||
getReferenceClientRect: null,
|
||||
content: this.element,
|
||||
interactive: true,
|
||||
trigger: "manual",
|
||||
placement: "top",
|
||||
hideOnClick: "toggle",
|
||||
trigger: 'manual',
|
||||
placement: 'top',
|
||||
hideOnClick: 'toggle',
|
||||
...this.tippyOptions,
|
||||
});
|
||||
|
||||
// maybe we have to hide tippy on its own blur event as well
|
||||
if (this.tippy.popper.firstChild) {
|
||||
(this.tippy.popper.firstChild as HTMLElement).addEventListener(
|
||||
"blur",
|
||||
(event) => {
|
||||
(this.tippy.popper.firstChild as HTMLElement).addEventListener('blur', (event) => {
|
||||
this.blurHandler({ event });
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
update(view: EditorView, oldState?: EditorState) {
|
||||
const { state, composing } = view;
|
||||
const { doc, selection } = state;
|
||||
const isSame =
|
||||
oldState && oldState.doc.eq(doc) && oldState.selection.eq(selection);
|
||||
const isSame = oldState && oldState.doc.eq(doc) && oldState.selection.eq(selection);
|
||||
|
||||
if (composing || isSame) {
|
||||
return;
|
||||
|
@ -242,21 +229,19 @@ export class BubbleMenuView {
|
|||
|
||||
destroy() {
|
||||
this.tippy?.destroy();
|
||||
this.element.removeEventListener("mousedown", this.mousedownHandler, {
|
||||
this.element.removeEventListener('mousedown', this.mousedownHandler, {
|
||||
capture: true,
|
||||
});
|
||||
this.view.dom.removeEventListener("dragstart", this.dragstartHandler);
|
||||
this.editor.off("focus", this.focusHandler);
|
||||
this.editor.off("blur", this.blurHandler);
|
||||
this.view.dom.removeEventListener('dragstart', this.dragstartHandler);
|
||||
this.editor.off('focus', this.focusHandler);
|
||||
this.editor.off('blur', this.blurHandler);
|
||||
}
|
||||
}
|
||||
|
||||
export const BubbleMenuPlugin = (options: BubbleMenuPluginProps) => {
|
||||
return new Plugin({
|
||||
key:
|
||||
typeof options.pluginKey === "string"
|
||||
? new PluginKey(options.pluginKey)
|
||||
: options.pluginKey,
|
||||
typeof options.pluginKey === 'string' ? new PluginKey(options.pluginKey) : options.pluginKey,
|
||||
view: (view) => new BubbleMenuView({ view, ...options }),
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { BubbleMenuPlugin, BubbleMenuPluginProps } from "./bubble-menu-plugin";
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { BubbleMenuPlugin, BubbleMenuPluginProps } from './bubble-menu-plugin';
|
||||
|
||||
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
|
||||
|
||||
export type BubbleMenuProps = Omit<
|
||||
Optional<BubbleMenuPluginProps, "pluginKey">,
|
||||
"element"
|
||||
> & {
|
||||
export type BubbleMenuProps = Omit<Optional<BubbleMenuPluginProps, 'pluginKey'>, 'element'> & {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
|
@ -23,7 +20,7 @@ export const BubbleMenu: React.FC<BubbleMenuProps> = (props) => {
|
|||
}
|
||||
|
||||
const {
|
||||
pluginKey = "bubbleMenu",
|
||||
pluginKey = 'bubbleMenu',
|
||||
editor,
|
||||
tippyOptions = {},
|
||||
shouldShow = null,
|
||||
|
@ -46,11 +43,7 @@ export const BubbleMenu: React.FC<BubbleMenuProps> = (props) => {
|
|||
}, [props.editor, element]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={setElement}
|
||||
className={props.className}
|
||||
style={{ visibility: "hidden" }}
|
||||
>
|
||||
<div ref={setElement} className={props.className} style={{ visibility: 'hidden' }}>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,52 +1,47 @@
|
|||
import React from "react";
|
||||
import { Dropdown } from "@douyinfe/semi-ui";
|
||||
import styles from "./style.module.scss";
|
||||
import React from 'react';
|
||||
import { Dropdown } from '@douyinfe/semi-ui';
|
||||
import styles from './style.module.scss';
|
||||
|
||||
const colors = [
|
||||
"rgb(23, 43, 77)",
|
||||
"rgb(7, 71, 166)",
|
||||
"rgb(0, 141, 166)",
|
||||
"rgb(0, 102, 68)",
|
||||
"rgb(255, 153, 31)",
|
||||
"rgb(191, 38, 0)",
|
||||
"rgb(64, 50, 148)",
|
||||
"rgb(151, 160, 175)",
|
||||
"rgb(76, 154, 255)",
|
||||
"rgb(0, 184, 217)",
|
||||
"rgb(54, 179, 126)",
|
||||
"rgb(255, 196, 0)",
|
||||
"rgb(255, 86, 48)",
|
||||
"rgb(101, 84, 192)",
|
||||
"rgb(255, 255, 255)",
|
||||
"rgb(179, 212, 255)",
|
||||
"rgb(179, 245, 255)",
|
||||
"rgb(171, 245, 209)",
|
||||
"rgb(255, 240, 179)",
|
||||
"rgb(255, 189, 173)",
|
||||
"rgb(234, 230, 255)",
|
||||
'rgb(23, 43, 77)',
|
||||
'rgb(7, 71, 166)',
|
||||
'rgb(0, 141, 166)',
|
||||
'rgb(0, 102, 68)',
|
||||
'rgb(255, 153, 31)',
|
||||
'rgb(191, 38, 0)',
|
||||
'rgb(64, 50, 148)',
|
||||
'rgb(151, 160, 175)',
|
||||
'rgb(76, 154, 255)',
|
||||
'rgb(0, 184, 217)',
|
||||
'rgb(54, 179, 126)',
|
||||
'rgb(255, 196, 0)',
|
||||
'rgb(255, 86, 48)',
|
||||
'rgb(101, 84, 192)',
|
||||
'rgb(255, 255, 255)',
|
||||
'rgb(179, 212, 255)',
|
||||
'rgb(179, 245, 255)',
|
||||
'rgb(171, 245, 209)',
|
||||
'rgb(255, 240, 179)',
|
||||
'rgb(255, 189, 173)',
|
||||
'rgb(234, 230, 255)',
|
||||
];
|
||||
|
||||
export const Color: React.FC<{
|
||||
onSetColor;
|
||||
disabled?: boolean;
|
||||
}> = ({ children, onSetColor, disabled = false }) => {
|
||||
if (disabled)
|
||||
return <span style={{ display: "inline-block" }}>{children}</span>;
|
||||
if (disabled) return <span style={{ display: 'inline-block' }}>{children}</span>;
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
zIndex={10000}
|
||||
trigger="click"
|
||||
position={"bottom"}
|
||||
position={'bottom'}
|
||||
render={
|
||||
<div className={styles.colorWrap}>
|
||||
{colors.map((color) => {
|
||||
return (
|
||||
<div
|
||||
key={color}
|
||||
className={styles.colorItem}
|
||||
onClick={() => onSetColor(color)}
|
||||
>
|
||||
<div key={color} className={styles.colorItem} onClick={() => onSetColor(color)}>
|
||||
<span style={{ backgroundColor: color }}></span>
|
||||
</div>
|
||||
);
|
||||
|
@ -54,7 +49,7 @@ export const Color: React.FC<{
|
|||
</div>
|
||||
}
|
||||
>
|
||||
<span style={{ display: "inline-block" }}>{children}</span>
|
||||
<span style={{ display: 'inline-block' }}>{children}</span>
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -2,11 +2,11 @@ export const Divider = () => {
|
|||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "inline-block",
|
||||
display: 'inline-block',
|
||||
width: 1,
|
||||
height: 24,
|
||||
margin: "0 6px",
|
||||
backgroundColor: "var(--semi-color-border)",
|
||||
margin: '0 6px',
|
||||
backgroundColor: 'var(--semi-color-border)',
|
||||
}}
|
||||
></div>
|
||||
);
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
import { Node, mergeAttributes } from "@tiptap/core";
|
||||
import {
|
||||
NodeViewWrapper,
|
||||
NodeViewContent,
|
||||
ReactNodeViewRenderer,
|
||||
} from "@tiptap/react";
|
||||
import { Button, Tooltip } from "@douyinfe/semi-ui";
|
||||
import { IconDownload } from "@douyinfe/semi-icons";
|
||||
import { download } from "../../utils/download";
|
||||
import styles from "./index.module.scss";
|
||||
import { Node, mergeAttributes } from '@tiptap/core';
|
||||
import { NodeViewWrapper, NodeViewContent, ReactNodeViewRenderer } from '@tiptap/react';
|
||||
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 { name, url } = node.attrs;
|
||||
|
@ -19,7 +15,7 @@ const Render = ({ node }) => {
|
|||
<span>
|
||||
<Tooltip zIndex={10000} content="下载">
|
||||
<Button
|
||||
theme={"borderless"}
|
||||
theme={'borderless'}
|
||||
type="tertiary"
|
||||
icon={<IconDownload />}
|
||||
onClick={() => download(url, name)}
|
||||
|
@ -33,27 +29,24 @@ const Render = ({ node }) => {
|
|||
};
|
||||
|
||||
export const Attachment = Node.create({
|
||||
name: "attachment",
|
||||
group: "block",
|
||||
name: 'attachment',
|
||||
group: 'block',
|
||||
draggable: true,
|
||||
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {
|
||||
class: "attachment",
|
||||
class: 'attachment',
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
return [{ tag: "div[class=attachment]" }];
|
||||
return [{ tag: 'div[class=attachment]' }];
|
||||
},
|
||||
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return [
|
||||
"div",
|
||||
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
|
||||
];
|
||||
return ['div', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
|
||||
},
|
||||
|
||||
addAttributes() {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { Extension } from "@tiptap/core";
|
||||
import "@tiptap/extension-text-style";
|
||||
import { Extension } from '@tiptap/core';
|
||||
import '@tiptap/extension-text-style';
|
||||
|
||||
export type ColorOptions = {
|
||||
types: string[];
|
||||
};
|
||||
|
||||
declare module "@tiptap/core" {
|
||||
declare module '@tiptap/core' {
|
||||
interface Commands<ReturnType> {
|
||||
backgroundColor: {
|
||||
/**
|
||||
|
@ -21,11 +21,11 @@ declare module "@tiptap/core" {
|
|||
}
|
||||
|
||||
export const BackgroundColor = Extension.create<ColorOptions>({
|
||||
name: "backgroundColor",
|
||||
name: 'backgroundColor',
|
||||
|
||||
addOptions() {
|
||||
return {
|
||||
types: ["textStyle"],
|
||||
types: ['textStyle'],
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -36,8 +36,7 @@ export const BackgroundColor = Extension.create<ColorOptions>({
|
|||
attributes: {
|
||||
backgroundColor: {
|
||||
default: null,
|
||||
parseHTML: (element) =>
|
||||
element.style.backgroundColor.replace(/['"]+/g, ""),
|
||||
parseHTML: (element) => element.style.backgroundColor.replace(/['"]+/g, ''),
|
||||
renderHTML: (attributes) => {
|
||||
if (!attributes.backgroundColor) {
|
||||
return {};
|
||||
|
@ -58,13 +57,13 @@ export const BackgroundColor = Extension.create<ColorOptions>({
|
|||
setBackgroundColor:
|
||||
(color) =>
|
||||
({ chain }) => {
|
||||
return chain().setMark("textStyle", { backgroundColor: color }).run();
|
||||
return chain().setMark('textStyle', { backgroundColor: color }).run();
|
||||
},
|
||||
unsetBackgroundColor:
|
||||
() =>
|
||||
({ chain }) => {
|
||||
return chain()
|
||||
.setMark("textStyle", { backgroundColor: null })
|
||||
.setMark('textStyle', { backgroundColor: null })
|
||||
.removeEmptyTextStyle()
|
||||
.run();
|
||||
},
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
import { Node, Command, mergeAttributes } from "@tiptap/core";
|
||||
import {
|
||||
NodeViewWrapper,
|
||||
NodeViewContent,
|
||||
ReactNodeViewRenderer,
|
||||
} from "@tiptap/react";
|
||||
import { Banner as SemiBanner } from "@douyinfe/semi-ui";
|
||||
import styles from "./index.module.scss";
|
||||
import { Node, Command, mergeAttributes } from '@tiptap/core';
|
||||
import { NodeViewWrapper, NodeViewContent, 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 {
|
||||
banner: {
|
||||
setBanner: () => Command;
|
||||
|
@ -16,34 +12,31 @@ declare module "@tiptap/core" {
|
|||
}
|
||||
|
||||
const BannerExtension = Node.create({
|
||||
name: "banner",
|
||||
content: "block*",
|
||||
group: "block",
|
||||
name: 'banner',
|
||||
content: 'block*',
|
||||
group: 'block',
|
||||
defining: true,
|
||||
draggable: true,
|
||||
|
||||
addAttributes() {
|
||||
return {
|
||||
type: {
|
||||
default: "info",
|
||||
default: 'info',
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
return [{ tag: "div" }];
|
||||
return [{ tag: 'div' }];
|
||||
},
|
||||
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return [
|
||||
"div",
|
||||
{ class: "banner" },
|
||||
'div',
|
||||
{ class: 'banner' },
|
||||
[
|
||||
"div",
|
||||
mergeAttributes(
|
||||
(this.options && this.options.HTMLAttributes) || {},
|
||||
HTMLAttributes
|
||||
),
|
||||
'div',
|
||||
mergeAttributes((this.options && this.options.HTMLAttributes) || {}, HTMLAttributes),
|
||||
0,
|
||||
],
|
||||
];
|
||||
|
|
|
@ -1,16 +1,12 @@
|
|||
import React, { useRef } from "react";
|
||||
import {
|
||||
ReactNodeViewRenderer,
|
||||
NodeViewWrapper,
|
||||
NodeViewContent,
|
||||
} 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";
|
||||
import React, { useRef } from 'react';
|
||||
import { ReactNodeViewRenderer, NodeViewWrapper, NodeViewContent } 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
|
||||
import { lowlight } from "lowlight";
|
||||
import { copy } from "helpers/copy";
|
||||
import styles from "./index.module.scss";
|
||||
import { lowlight } from 'lowlight';
|
||||
import { copy } from 'helpers/copy';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const Render = ({
|
||||
editor,
|
||||
|
@ -29,7 +25,7 @@ const Render = ({
|
|||
{isEditable && (
|
||||
<Select
|
||||
size="small"
|
||||
defaultValue={defaultLanguage || "null"}
|
||||
defaultValue={defaultLanguage || 'null'}
|
||||
onChange={(value) => updateAttributes({ language: value })}
|
||||
className={styles.selectorWrap}
|
||||
>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { Extension } from "@tiptap/core";
|
||||
import { Plugin } from "prosemirror-state";
|
||||
import findColors from "../utils/find-colors";
|
||||
import { Extension } from '@tiptap/core';
|
||||
import { Plugin } from 'prosemirror-state';
|
||||
import findColors from '../utils/find-colors';
|
||||
|
||||
export const ColorHighlighter = Extension.create({
|
||||
name: "colorHighlighter",
|
||||
name: 'colorHighlighter',
|
||||
|
||||
addProseMirrorPlugins() {
|
||||
return [
|
||||
|
@ -13,9 +13,7 @@ export const ColorHighlighter = Extension.create({
|
|||
return findColors(doc);
|
||||
},
|
||||
apply(transaction, oldState) {
|
||||
return transaction.docChanged
|
||||
? findColors(transaction.doc)
|
||||
: oldState;
|
||||
return transaction.docChanged ? findColors(transaction.doc) : oldState;
|
||||
},
|
||||
},
|
||||
props: {
|
||||
|
|
|
@ -1,24 +1,20 @@
|
|||
import { Node, mergeAttributes } from "@tiptap/core";
|
||||
import { Node, mergeAttributes } from '@tiptap/core';
|
||||
|
||||
export const Div = Node.create({
|
||||
name: "div",
|
||||
name: 'div',
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {},
|
||||
};
|
||||
},
|
||||
content: "block*",
|
||||
group: "block",
|
||||
content: 'block*',
|
||||
group: 'block',
|
||||
defining: true,
|
||||
parseHTML() {
|
||||
return [{ tag: "div" }];
|
||||
return [{ tag: 'div' }];
|
||||
},
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return [
|
||||
"div",
|
||||
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
|
||||
0,
|
||||
];
|
||||
return ['div', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
|
||||
},
|
||||
// @ts-ignore
|
||||
addCommands() {
|
||||
|
@ -26,17 +22,17 @@ export const Div = Node.create({
|
|||
setDiv:
|
||||
(attributes) =>
|
||||
({ commands }) => {
|
||||
return commands.wrapIn("div", attributes);
|
||||
return commands.wrapIn('div', attributes);
|
||||
},
|
||||
toggleDiv:
|
||||
(attributes) =>
|
||||
({ commands }) => {
|
||||
return commands.toggleWrap("div", attributes);
|
||||
return commands.toggleWrap('div', attributes);
|
||||
},
|
||||
unsetDiv:
|
||||
(attributes) =>
|
||||
({ commands }) => {
|
||||
return commands.lift("div");
|
||||
return commands.lift('div');
|
||||
},
|
||||
};
|
||||
},
|
||||
|
|
|
@ -5,24 +5,20 @@ import {
|
|||
textInputRule,
|
||||
textblockTypeInputRule,
|
||||
wrappingInputRule,
|
||||
} from "@tiptap/core";
|
||||
import {
|
||||
NodeViewWrapper,
|
||||
NodeViewContent,
|
||||
ReactNodeViewRenderer,
|
||||
} from "@tiptap/react";
|
||||
import { useRouter } from "next/router";
|
||||
import Link from "next/link";
|
||||
import { Space, Popover, Tag, Input, Typography } from "@douyinfe/semi-ui";
|
||||
import { useChildrenDocument } 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";
|
||||
} from '@tiptap/core';
|
||||
import { NodeViewWrapper, NodeViewContent, ReactNodeViewRenderer } from '@tiptap/react';
|
||||
import { useRouter } from 'next/router';
|
||||
import Link from 'next/link';
|
||||
import { Space, Popover, Tag, Input, Typography } from '@douyinfe/semi-ui';
|
||||
import { useChildrenDocument } 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;
|
||||
|
||||
declare module "@tiptap/core" {
|
||||
declare module '@tiptap/core' {
|
||||
interface Commands {
|
||||
documentChildren: {
|
||||
setDocumentChildren: () => Command;
|
||||
|
@ -33,33 +29,30 @@ declare module "@tiptap/core" {
|
|||
export const DocumentChildrenInputRegex = /^documentChildren\$$/;
|
||||
|
||||
const DocumentChildrenExtension = Node.create({
|
||||
name: "documentChildren",
|
||||
group: "block",
|
||||
name: 'documentChildren',
|
||||
group: 'block',
|
||||
defining: true,
|
||||
draggable: true,
|
||||
|
||||
addAttributes() {
|
||||
return {
|
||||
color: {
|
||||
default: "grey",
|
||||
default: 'grey',
|
||||
},
|
||||
text: {
|
||||
default: "",
|
||||
default: '',
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
return [{ tag: "div[data-type=documentChildren]" }];
|
||||
return [{ tag: 'div[data-type=documentChildren]' }];
|
||||
},
|
||||
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return [
|
||||
"div",
|
||||
mergeAttributes(
|
||||
(this.options && this.options.HTMLAttributes) || {},
|
||||
HTMLAttributes
|
||||
),
|
||||
'div',
|
||||
mergeAttributes((this.options && this.options.HTMLAttributes) || {}, HTMLAttributes),
|
||||
];
|
||||
},
|
||||
|
||||
|
@ -91,12 +84,8 @@ const Render = () => {
|
|||
const { pathname, query } = useRouter();
|
||||
const wikiId = query?.wikiId;
|
||||
const documentId = query?.documentId;
|
||||
const isShare = pathname.includes("share");
|
||||
const {
|
||||
data: documents,
|
||||
loading,
|
||||
error,
|
||||
} = useChildrenDocument({ wikiId, documentId, isShare });
|
||||
const isShare = pathname.includes('share');
|
||||
const { data: documents, loading, error } = useChildrenDocument({ wikiId, documentId, isShare });
|
||||
|
||||
return (
|
||||
<NodeViewWrapper as="div" className={styles.wrap}>
|
||||
|
@ -120,7 +109,7 @@ const Render = () => {
|
|||
key={doc.id}
|
||||
href={{
|
||||
pathname: `${
|
||||
!isShare ? "" : "/share"
|
||||
!isShare ? '' : '/share'
|
||||
}/wiki/[wikiId]/document/[documentId]`,
|
||||
query: { wikiId: doc.wikiId, documentId: doc.id },
|
||||
}}
|
||||
|
|
|
@ -5,32 +5,21 @@ import {
|
|||
textInputRule,
|
||||
textblockTypeInputRule,
|
||||
wrappingInputRule,
|
||||
} from "@tiptap/core";
|
||||
import {
|
||||
NodeViewWrapper,
|
||||
NodeViewContent,
|
||||
ReactNodeViewRenderer,
|
||||
} from "@tiptap/react";
|
||||
import { useRouter } from "next/router";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
Space,
|
||||
Select,
|
||||
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";
|
||||
} from '@tiptap/core';
|
||||
import { NodeViewWrapper, NodeViewContent, ReactNodeViewRenderer } from '@tiptap/react';
|
||||
import { useRouter } from 'next/router';
|
||||
import Link from 'next/link';
|
||||
import { Space, Select, 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;
|
||||
|
||||
declare module "@tiptap/core" {
|
||||
declare module '@tiptap/core' {
|
||||
interface Commands {
|
||||
documentReference: {
|
||||
setDocumentReference: () => Command;
|
||||
|
@ -41,36 +30,33 @@ declare module "@tiptap/core" {
|
|||
export const DocumentReferenceInputRegex = /^documentReference\$$/;
|
||||
|
||||
const DocumentReferenceExtension = Node.create({
|
||||
name: "documentReference",
|
||||
group: "block",
|
||||
name: 'documentReference',
|
||||
group: 'block',
|
||||
defining: true,
|
||||
draggable: true,
|
||||
|
||||
addAttributes() {
|
||||
return {
|
||||
wikiId: {
|
||||
default: "",
|
||||
default: '',
|
||||
},
|
||||
documentId: {
|
||||
default: "",
|
||||
default: '',
|
||||
},
|
||||
title: {
|
||||
default: "",
|
||||
default: '',
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
return [{ tag: "div[data-type=documentReference]" }];
|
||||
return [{ tag: 'div[data-type=documentReference]' }];
|
||||
},
|
||||
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return [
|
||||
"div",
|
||||
mergeAttributes(
|
||||
(this.options && this.options.HTMLAttributes) || {},
|
||||
HTMLAttributes
|
||||
),
|
||||
'div',
|
||||
mergeAttributes((this.options && this.options.HTMLAttributes) || {}, HTMLAttributes),
|
||||
];
|
||||
},
|
||||
|
||||
|
@ -101,14 +87,10 @@ const DocumentReferenceExtension = Node.create({
|
|||
const Render = ({ editor, node, updateAttributes }) => {
|
||||
const { pathname, query } = useRouter();
|
||||
const wikiIdFromUrl = query?.wikiId;
|
||||
const isShare = pathname.includes("share");
|
||||
const isShare = pathname.includes('share');
|
||||
const isEditable = editor.isEditable;
|
||||
const { wikiId, documentId, title } = node.attrs;
|
||||
const {
|
||||
data: tocs,
|
||||
loading,
|
||||
error,
|
||||
} = useWikiTocs(isShare ? null : wikiIdFromUrl);
|
||||
const { data: tocs, loading, error } = useWikiTocs(isShare ? null : wikiIdFromUrl);
|
||||
|
||||
return (
|
||||
<NodeViewWrapper as="div" className={styles.wrap}>
|
||||
|
@ -141,9 +123,7 @@ const Render = ({ editor, node, updateAttributes }) => {
|
|||
<Link
|
||||
key={documentId}
|
||||
href={{
|
||||
pathname: `${
|
||||
!isShare ? "" : "/share"
|
||||
}/wiki/[wikiId]/document/[documentId]`,
|
||||
pathname: `${!isShare ? '' : '/share'}/wiki/[wikiId]/document/[documentId]`,
|
||||
query: { wikiId, documentId },
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { Extension } from "@tiptap/core";
|
||||
import "@tiptap/extension-text-style";
|
||||
import { Extension } from '@tiptap/core';
|
||||
import '@tiptap/extension-text-style';
|
||||
|
||||
type FontSizeOptions = {
|
||||
types: string[];
|
||||
};
|
||||
|
||||
declare module "@tiptap/core" {
|
||||
declare module '@tiptap/core' {
|
||||
interface Commands<ReturnType> {
|
||||
fontSize: {
|
||||
setFontSize: (size: string) => ReturnType;
|
||||
|
@ -15,11 +15,11 @@ declare module "@tiptap/core" {
|
|||
}
|
||||
|
||||
export const FontSize = Extension.create<FontSizeOptions>({
|
||||
name: "fontSize",
|
||||
name: 'fontSize',
|
||||
|
||||
addOptions() {
|
||||
return {
|
||||
types: ["textStyle"],
|
||||
types: ['textStyle'],
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -30,8 +30,7 @@ export const FontSize = Extension.create<FontSizeOptions>({
|
|||
attributes: {
|
||||
fontSize: {
|
||||
default: null,
|
||||
parseHTML: (element) =>
|
||||
element.style.fontSize.replace(/['"]+/g, ""),
|
||||
parseHTML: (element) => element.style.fontSize.replace(/['"]+/g, ''),
|
||||
renderHTML: (attributes) => {
|
||||
if (!attributes.fontSize) {
|
||||
return {};
|
||||
|
@ -52,15 +51,12 @@ export const FontSize = Extension.create<FontSizeOptions>({
|
|||
setFontSize:
|
||||
(fontSize) =>
|
||||
({ chain }) => {
|
||||
return chain().setMark("textStyle", { fontSize }).run();
|
||||
return chain().setMark('textStyle', { fontSize }).run();
|
||||
},
|
||||
unsetFontSize:
|
||||
() =>
|
||||
({ chain }) => {
|
||||
return chain()
|
||||
.setMark("textStyle", { fontSize: null })
|
||||
.removeEmptyTextStyle()
|
||||
.run();
|
||||
return chain().setMark('textStyle', { fontSize: null }).removeEmptyTextStyle().run();
|
||||
},
|
||||
};
|
||||
},
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { Node, nodeInputRule, mergeAttributes } from "@tiptap/core";
|
||||
import { TextSelection } from "prosemirror-state";
|
||||
import { Node, nodeInputRule, mergeAttributes } from '@tiptap/core';
|
||||
import { TextSelection } from 'prosemirror-state';
|
||||
|
||||
export interface HorizontalRuleOptions {
|
||||
HTMLAttributes: Record<string, any>;
|
||||
}
|
||||
|
||||
declare module "@tiptap/core" {
|
||||
declare module '@tiptap/core' {
|
||||
interface Commands<ReturnType> {
|
||||
horizontalRule: {
|
||||
/**
|
||||
|
@ -17,26 +17,23 @@ declare module "@tiptap/core" {
|
|||
}
|
||||
|
||||
export const HorizontalRule = Node.create<HorizontalRuleOptions>({
|
||||
name: "horizontalRule",
|
||||
group: "block",
|
||||
name: 'horizontalRule',
|
||||
group: 'block',
|
||||
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {
|
||||
class: "hr-line",
|
||||
class: 'hr-line',
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
return [{ tag: "div[class=hr-line]" }];
|
||||
return [{ tag: 'div[class=hr-line]' }];
|
||||
},
|
||||
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return [
|
||||
"div",
|
||||
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
|
||||
];
|
||||
return ['div', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
|
@ -57,8 +54,7 @@ export const HorizontalRule = Node.create<HorizontalRuleOptions>({
|
|||
tr.setSelection(TextSelection.create(tr.doc, $to.pos));
|
||||
} else {
|
||||
// add node after horizontal rule if it’s the end of the document
|
||||
const node =
|
||||
$to.parent.type.contentMatch.defaultType?.create();
|
||||
const node = $to.parent.type.contentMatch.defaultType?.create();
|
||||
|
||||
if (node) {
|
||||
tr.insert(posAfter, node);
|
||||
|
|
|
@ -1,24 +1,20 @@
|
|||
import { Node, mergeAttributes } from "@tiptap/core";
|
||||
import {
|
||||
NodeViewWrapper,
|
||||
NodeViewContent,
|
||||
ReactNodeViewRenderer,
|
||||
} from "@tiptap/react";
|
||||
import { Input } from "@douyinfe/semi-ui";
|
||||
import { Resizeable } from "components/resizeable";
|
||||
import styles from "./index.module.scss";
|
||||
import { Node, mergeAttributes } from '@tiptap/core';
|
||||
import { NodeViewWrapper, NodeViewContent, ReactNodeViewRenderer } from '@tiptap/react';
|
||||
import { Input } from '@douyinfe/semi-ui';
|
||||
import { Resizeable } from 'components/resizeable';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const IframeNode = Node.create({
|
||||
name: "external-iframe",
|
||||
content: "",
|
||||
marks: "",
|
||||
group: "block",
|
||||
name: 'external-iframe',
|
||||
content: '',
|
||||
marks: '',
|
||||
group: 'block',
|
||||
draggable: true,
|
||||
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {
|
||||
"data-type": "external-iframe",
|
||||
'data-type': 'external-iframe',
|
||||
},
|
||||
};
|
||||
},
|
||||
|
@ -26,7 +22,7 @@ const IframeNode = Node.create({
|
|||
addAttributes() {
|
||||
return {
|
||||
width: {
|
||||
default: "100%",
|
||||
default: '100%',
|
||||
},
|
||||
height: {
|
||||
default: 54,
|
||||
|
@ -46,10 +42,7 @@ const IframeNode = Node.create({
|
|||
},
|
||||
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return [
|
||||
"iframe",
|
||||
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
|
||||
];
|
||||
return ['iframe', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
|
||||
},
|
||||
|
||||
// @ts-ignore
|
||||
|
@ -91,17 +84,14 @@ const Render = ({ editor, node, updateAttributes }) => {
|
|||
{isEditable && (
|
||||
<div className={styles.handlerWrap}>
|
||||
<Input
|
||||
placeholder={"输入外链地址"}
|
||||
placeholder={'输入外链地址'}
|
||||
value={url}
|
||||
onChange={(url) => updateAttributes({ url })}
|
||||
></Input>
|
||||
</div>
|
||||
)}
|
||||
{url && (
|
||||
<div
|
||||
className={styles.innerWrap}
|
||||
style={{ pointerEvents: !isEditable ? "auto" : "none" }}
|
||||
>
|
||||
<div className={styles.innerWrap} style={{ pointerEvents: !isEditable ? 'auto' : 'none' }}>
|
||||
<iframe src={url}></iframe>
|
||||
</div>
|
||||
)}
|
||||
|
@ -119,7 +109,7 @@ const Render = ({ editor, node, updateAttributes }) => {
|
|||
{content}
|
||||
</Resizeable>
|
||||
) : (
|
||||
<div style={{ width, height, maxWidth: "100%" }}>{content}</div>
|
||||
<div style={{ width, height, maxWidth: '100%' }}>{content}</div>
|
||||
)}
|
||||
</NodeViewWrapper>
|
||||
);
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
import { Plugin } from "prosemirror-state";
|
||||
import { Image as TImage } from "@tiptap/extension-image";
|
||||
import {
|
||||
NodeViewWrapper,
|
||||
NodeViewContent,
|
||||
ReactNodeViewRenderer,
|
||||
} from "@tiptap/react";
|
||||
import { Resizeable } from "components/resizeable";
|
||||
import { uploadFile } from "services/file";
|
||||
import { Plugin } from 'prosemirror-state';
|
||||
import { Image as TImage } from '@tiptap/extension-image';
|
||||
import { NodeViewWrapper, NodeViewContent, ReactNodeViewRenderer } from '@tiptap/react';
|
||||
import { Resizeable } from 'components/resizeable';
|
||||
import { uploadFile } from 'services/file';
|
||||
|
||||
const Render = ({ editor, node, updateAttributes }) => {
|
||||
const isEditable = editor.isEditable;
|
||||
|
@ -16,25 +12,16 @@ const Render = ({ editor, node, updateAttributes }) => {
|
|||
updateAttributes({ height: size.height, width: size.width });
|
||||
};
|
||||
|
||||
const content = src && (
|
||||
<img src={src} alt={title} style={{ width: "100%", height: "100%" }} />
|
||||
);
|
||||
const content = src && <img src={src} alt={title} style={{ width: '100%', height: '100%' }} />;
|
||||
|
||||
return (
|
||||
<NodeViewWrapper
|
||||
as="div"
|
||||
style={{ textAlign, fontSize: 0, maxWidth: "100%" }}
|
||||
>
|
||||
<NodeViewWrapper as="div" style={{ textAlign, fontSize: 0, maxWidth: '100%' }}>
|
||||
{isEditable ? (
|
||||
<Resizeable width={width} height={height} onChange={onResize}>
|
||||
{content}
|
||||
</Resizeable>
|
||||
) : (
|
||||
<div
|
||||
style={{ display: "inline-block", width, height, maxWidth: "100%" }}
|
||||
>
|
||||
{content}
|
||||
</div>
|
||||
<div style={{ display: 'inline-block', width, height, maxWidth: '100%' }}>{content}</div>
|
||||
)}
|
||||
</NodeViewWrapper>
|
||||
);
|
||||
|
@ -54,10 +41,10 @@ export const Image = TImage.extend({
|
|||
default: null,
|
||||
},
|
||||
width: {
|
||||
default: "auto",
|
||||
default: 'auto',
|
||||
},
|
||||
height: {
|
||||
default: "auto",
|
||||
default: 'auto',
|
||||
},
|
||||
};
|
||||
},
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { Command, Extension } from "@tiptap/core";
|
||||
import { sinkListItem, liftListItem } from "prosemirror-schema-list";
|
||||
import { TextSelection, AllSelection, Transaction } from "prosemirror-state";
|
||||
import { clamp } from "../utils/shared";
|
||||
import { isListActive } from "../utils/active";
|
||||
import { getNodeType } from "../utils/type";
|
||||
import { isListNode } from "../utils/node";
|
||||
import { Command, Extension } from '@tiptap/core';
|
||||
import { sinkListItem, liftListItem } from 'prosemirror-schema-list';
|
||||
import { TextSelection, AllSelection, Transaction } from 'prosemirror-state';
|
||||
import { clamp } from '../utils/shared';
|
||||
import { isListActive } from '../utils/active';
|
||||
import { getNodeType } from '../utils/type';
|
||||
import { isListNode } from '../utils/node';
|
||||
|
||||
type IndentOptions = {
|
||||
types: string[];
|
||||
|
@ -12,7 +12,7 @@ type IndentOptions = {
|
|||
defaultIndentLevel: number;
|
||||
};
|
||||
|
||||
declare module "@tiptap/core" {
|
||||
declare module '@tiptap/core' {
|
||||
interface Commands {
|
||||
indent: {
|
||||
indent: () => Command;
|
||||
|
@ -28,11 +28,7 @@ export enum IndentProps {
|
|||
less = -30,
|
||||
}
|
||||
|
||||
function setNodeIndentMarkup(
|
||||
tr: Transaction,
|
||||
pos: number,
|
||||
delta: number
|
||||
): Transaction {
|
||||
function setNodeIndentMarkup(tr: Transaction, pos: number, delta: number): Transaction {
|
||||
if (!tr.doc) return tr;
|
||||
|
||||
const node = tr.doc.nodeAt(pos);
|
||||
|
@ -58,9 +54,7 @@ function updateIndentLevel(tr: Transaction, delta: number): Transaction {
|
|||
|
||||
if (!doc || !selection) return tr;
|
||||
|
||||
if (
|
||||
!(selection instanceof TextSelection || selection instanceof AllSelection)
|
||||
) {
|
||||
if (!(selection instanceof TextSelection || selection instanceof AllSelection)) {
|
||||
return tr;
|
||||
}
|
||||
|
||||
|
@ -69,7 +63,7 @@ function updateIndentLevel(tr: Transaction, delta: number): Transaction {
|
|||
doc.nodesBetween(from, to, (node, pos) => {
|
||||
const nodeType = node.type;
|
||||
|
||||
if (nodeType.name === "paragraph" || nodeType.name === "heading") {
|
||||
if (nodeType.name === 'paragraph' || nodeType.name === 'heading') {
|
||||
tr = setNodeIndentMarkup(tr, pos, delta);
|
||||
return false;
|
||||
}
|
||||
|
@ -83,11 +77,11 @@ function updateIndentLevel(tr: Transaction, delta: number): Transaction {
|
|||
}
|
||||
|
||||
export const Indent = Extension.create<IndentOptions>({
|
||||
name: "indent",
|
||||
name: 'indent',
|
||||
|
||||
addOptions() {
|
||||
return {
|
||||
types: ["heading", "paragraph"],
|
||||
types: ['heading', 'paragraph'],
|
||||
indentLevels: [0, 30, 60, 90, 120, 150, 180, 210],
|
||||
defaultIndentLevel: 0,
|
||||
};
|
||||
|
@ -104,8 +98,7 @@ export const Indent = Extension.create<IndentOptions>({
|
|||
style: `margin-left: ${attributes.indent}px!important;`,
|
||||
}),
|
||||
parseHTML: (element) =>
|
||||
parseInt(element.style.marginLeft) ||
|
||||
this.options.defaultIndentLevel,
|
||||
parseInt(element.style.marginLeft) || this.options.defaultIndentLevel,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -118,9 +111,7 @@ export const Indent = Extension.create<IndentOptions>({
|
|||
() =>
|
||||
({ tr, state, dispatch }) => {
|
||||
if (isListActive(this.editor)) {
|
||||
const name = this.editor.can().liftListItem("taskItem")
|
||||
? "taskItem"
|
||||
: "listItem";
|
||||
const name = this.editor.can().liftListItem('taskItem') ? 'taskItem' : 'listItem';
|
||||
const type = getNodeType(name, state.schema);
|
||||
return sinkListItem(type)(state, dispatch);
|
||||
}
|
||||
|
@ -140,9 +131,7 @@ export const Indent = Extension.create<IndentOptions>({
|
|||
() =>
|
||||
({ tr, state, dispatch }) => {
|
||||
if (isListActive(this.editor)) {
|
||||
const name = this.editor.can().liftListItem("taskItem")
|
||||
? "taskItem"
|
||||
: "listItem";
|
||||
const name = this.editor.can().liftListItem('taskItem') ? 'taskItem' : 'listItem';
|
||||
const type = getNodeType(name, state.schema);
|
||||
return liftListItem(type)(state, dispatch);
|
||||
}
|
||||
|
@ -164,10 +153,10 @@ export const Indent = Extension.create<IndentOptions>({
|
|||
// @ts-ignore
|
||||
addKeyboardShortcuts() {
|
||||
return {
|
||||
Tab: () => {
|
||||
'Tab': () => {
|
||||
return this.editor.commands.indent();
|
||||
},
|
||||
"Shift-Tab": () => {
|
||||
'Shift-Tab': () => {
|
||||
return this.editor.commands.outdent();
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,21 +1,12 @@
|
|||
import {
|
||||
Node,
|
||||
Command,
|
||||
mergeAttributes,
|
||||
wrappingInputRule,
|
||||
} from "@tiptap/core";
|
||||
import {
|
||||
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";
|
||||
import { Node, Command, mergeAttributes, wrappingInputRule } from '@tiptap/core';
|
||||
import { 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 {
|
||||
katex: {
|
||||
setKatex: () => Command;
|
||||
|
@ -28,30 +19,27 @@ const { Text } = Typography;
|
|||
export const KatexInputRegex = /^\$\$(.+)?\$$/;
|
||||
|
||||
const KatexExtension = Node.create({
|
||||
name: "katex",
|
||||
group: "block",
|
||||
name: 'katex',
|
||||
group: 'block',
|
||||
defining: true,
|
||||
draggable: true,
|
||||
|
||||
addAttributes() {
|
||||
return {
|
||||
text: {
|
||||
default: "",
|
||||
default: '',
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
return [{ tag: "div[data-type=katex]" }];
|
||||
return [{ tag: 'div[data-type=katex]' }];
|
||||
},
|
||||
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return [
|
||||
"div",
|
||||
mergeAttributes(
|
||||
(this.options && this.options.HTMLAttributes) || {},
|
||||
HTMLAttributes
|
||||
),
|
||||
'div',
|
||||
mergeAttributes((this.options && this.options.HTMLAttributes) || {}, HTMLAttributes),
|
||||
];
|
||||
},
|
||||
|
||||
|
@ -94,10 +82,7 @@ const Render = ({ editor, node, updateAttributes }) => {
|
|||
}, [text]);
|
||||
|
||||
const content = text ? (
|
||||
<span
|
||||
contentEditable={false}
|
||||
dangerouslySetInnerHTML={{ __html: formatText }}
|
||||
></span>
|
||||
<span contentEditable={false} dangerouslySetInnerHTML={{ __html: formatText }}></span>
|
||||
) : (
|
||||
<span contentEditable={false}>请输入公式</span>
|
||||
);
|
||||
|
@ -118,10 +103,7 @@ const Render = ({ editor, node, updateAttributes }) => {
|
|||
onChange={(v) => updateAttributes({ text: v })}
|
||||
style={{ marginBottom: 8 }}
|
||||
/>
|
||||
<Text
|
||||
type="tertiary"
|
||||
link={{ href: "https://katex.org/", target: "_blank" }}
|
||||
>
|
||||
<Text type="tertiary" link={{ href: 'https://katex.org/', target: '_blank' }}>
|
||||
<Space>
|
||||
<IconHelpCircle />
|
||||
查看帮助文档
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue