chore: format code

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

View File

@ -20,11 +20,7 @@
"pm2": "concurrently \"pnpm:pm2:*\"",
"pm2: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"
}

View File

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

View File

@ -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"
}

View File

@ -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>

View File

@ -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,

View File

@ -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);

View File

@ -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 && (

View File

@ -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>

View File

@ -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,48 +32,38 @@ 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 handle = (evt) => {
const data = {
...docAuth.auth,
userName: docAuth.user.name,
};
data[authKey] = evt.target.checked;
onChange(data);
const renderChecked = (onChange, authKey: 'readable' | 'editable') => (checked, docAuth) => {
const handle = (evt) => {
const data = {
...docAuth.auth,
userName: docAuth.user.name,
};
return (
<Checkbox
style={{ display: "inline-block" }}
checked={checked}
onChange={handle}
/>
);
data[authKey] = evt.target.checked;
onChange(data);
};
export const DocumentCollaboration: React.FC<IProps> = ({
wikiId,
documentId,
}) => {
return <Checkbox style={{ display: 'inline-block' }} checked={checked} onChange={handle} />;
};
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>
)}
/>

View File

@ -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>

View File

@ -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}

View File

@ -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>

View File

@ -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('')}
/>
}
/>

View File

@ -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]);

View File

@ -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>
);

View File

@ -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>

View File

@ -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 />

View File

@ -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),
};
}

View File

@ -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');
};
}, []);

View File

@ -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')} />
</>
);
}}

View File

@ -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
}
/>
</>

View File

@ -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>

View File

@ -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>

View File

@ -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();

View File

@ -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>

View File

@ -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>

View File

@ -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 (

View File

@ -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>
);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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}

View File

@ -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}

View File

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

View File

@ -1,18 +1,11 @@
import { Icon } from "@douyinfe/semi-ui";
import { Icon } from '@douyinfe/semi-ui';
export const IconFont: React.FC<{ style?: React.CSSProperties }> = ({
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"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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}

View File

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

View File

@ -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}

View File

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

View File

@ -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}

View File

@ -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}

View File

@ -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}

View File

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

View File

@ -1,18 +1,12 @@
import { Icon } from "@douyinfe/semi-ui";
import { Icon } from '@douyinfe/semi-ui';
export const IconStatus: React.FC<{ style?: React.CSSProperties }> = ({
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"

View File

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

View File

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

View File

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

View File

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

View File

@ -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';

View File

@ -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>;
};

View File

@ -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>

View File

@ -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 }} />
)}
</>
}

View File

@ -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

View File

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

View File

@ -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>

View File

@ -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>

View File

@ -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;

View File

@ -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>

View File

@ -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>
);

View File

@ -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

View File

@ -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}

View File

@ -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 (

View File

@ -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;

View File

@ -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>
);
};

View File

@ -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,

View File

@ -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.blurHandler({ 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 }),
});
};

View File

@ -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>
);

View File

@ -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>
);
};

View File

@ -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>
);

View File

@ -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() {

View File

@ -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();
},

View File

@ -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,
],
];

View File

@ -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}
>

View File

@ -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: {

View File

@ -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');
},
};
},

View File

@ -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 },
}}

View File

@ -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 },
}}
>

View File

@ -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();
},
};
},

View File

@ -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 its 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);

View File

@ -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>
);

View File

@ -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',
},
};
},

View File

@ -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();
},
};

View File

@ -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