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