mirror of https://github.com/fantasticit/think.git
tiptap: improve document-reference menu
parent
9042da011f
commit
c9d4fd5127
|
@ -34,6 +34,7 @@ import { Search } from './menus/search';
|
||||||
|
|
||||||
import { Banner } from './menus/banner';
|
import { Banner } from './menus/banner';
|
||||||
import { Countdonw } from './menus/countdown';
|
import { Countdonw } from './menus/countdown';
|
||||||
|
import { DocumentReference } from './menus/document-reference';
|
||||||
import { Image } from './menus/image';
|
import { Image } from './menus/image';
|
||||||
import { Iframe } from './menus/iframe';
|
import { Iframe } from './menus/iframe';
|
||||||
import { Table } from './menus/table';
|
import { Table } from './menus/table';
|
||||||
|
@ -89,6 +90,7 @@ export const MenuBar: React.FC<{ editor: any }> = ({ editor }) => {
|
||||||
|
|
||||||
<Banner editor={editor} />
|
<Banner editor={editor} />
|
||||||
<Countdonw editor={editor} />
|
<Countdonw editor={editor} />
|
||||||
|
<DocumentReference editor={editor} />
|
||||||
<Image editor={editor} />
|
<Image editor={editor} />
|
||||||
<Iframe editor={editor} />
|
<Iframe editor={editor} />
|
||||||
<Table editor={editor} />
|
<Table editor={editor} />
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import { Space, Button, List, Popover } from '@douyinfe/semi-ui';
|
||||||
|
import { IconEdit, IconDelete } from '@douyinfe/semi-icons';
|
||||||
|
import { Tooltip } from 'components/tooltip';
|
||||||
|
import { DataRender } from 'components/data-render';
|
||||||
|
import { useWikiTocs } from 'data/wiki';
|
||||||
|
import { BubbleMenu } from '../../views/bubble-menu';
|
||||||
|
import { DocumentReference } from '../../extensions/document-reference';
|
||||||
|
import { Divider } from '../../divider';
|
||||||
|
|
||||||
|
export const DocumentReferenceBubbleMenu = ({ editor }) => {
|
||||||
|
const { pathname, query } = useRouter();
|
||||||
|
const wikiIdFromUrl = query?.wikiId;
|
||||||
|
const isShare = pathname.includes('share');
|
||||||
|
const { data: tocs, loading, error } = useWikiTocs(isShare ? null : wikiIdFromUrl);
|
||||||
|
|
||||||
|
const selectDoc = useCallback(
|
||||||
|
(item) => {
|
||||||
|
const { wikiId, title, id: documentId } = item;
|
||||||
|
|
||||||
|
editor
|
||||||
|
.chain()
|
||||||
|
.updateAttributes(DocumentReference.name, { wikiId, documentId, title })
|
||||||
|
.setNodeSelection(editor.state.selection.from)
|
||||||
|
.focus()
|
||||||
|
.run();
|
||||||
|
},
|
||||||
|
[editor]
|
||||||
|
);
|
||||||
|
|
||||||
|
const deleteNode = useCallback(() => editor.chain().deleteSelection().run(), [editor]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BubbleMenu
|
||||||
|
className={'bubble-menu'}
|
||||||
|
editor={editor}
|
||||||
|
pluginKey="countdonw-bubble-menu"
|
||||||
|
shouldShow={() => editor.isActive(DocumentReference.name)}
|
||||||
|
tippyOptions={{ maxWidth: 456 }}
|
||||||
|
>
|
||||||
|
<Space>
|
||||||
|
<Popover
|
||||||
|
spacing={10}
|
||||||
|
content={
|
||||||
|
<DataRender
|
||||||
|
loading={loading}
|
||||||
|
error={error}
|
||||||
|
normalContent={() => (
|
||||||
|
<List
|
||||||
|
style={{ maxHeight: 320, overflow: 'auto' }}
|
||||||
|
dataSource={tocs}
|
||||||
|
renderItem={(item) => (
|
||||||
|
<List.Item
|
||||||
|
onClick={() => selectDoc(item)}
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
main={<span style={{ color: 'var(--semi-color-text-0)', fontWeight: 500 }}>{item.title}</span>}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
trigger="click"
|
||||||
|
>
|
||||||
|
<Button size="small" type="tertiary" theme="borderless" icon={<IconEdit />} />
|
||||||
|
</Popover>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<Tooltip content="删除节点" hideOnClick>
|
||||||
|
<Button onClick={deleteNode} icon={<IconDelete />} type="tertiary" theme="borderless" size="small" />
|
||||||
|
</Tooltip>
|
||||||
|
</Space>
|
||||||
|
</BubbleMenu>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,15 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Editor } from '@tiptap/core';
|
||||||
|
import { DocumentReferenceBubbleMenu } from './bubble';
|
||||||
|
|
||||||
|
export const DocumentReference: React.FC<{ editor: Editor }> = ({ editor }) => {
|
||||||
|
if (!editor) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DocumentReferenceBubbleMenu editor={editor} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,5 +1,5 @@
|
||||||
.wrap {
|
.wrap {
|
||||||
margin-top: .75em;
|
margin-top: 0.75em;
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
|
|
||||||
.itemWrap {
|
.itemWrap {
|
||||||
|
@ -7,7 +7,6 @@
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
color: var(--node-text-color);
|
color: var(--node-text-color);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
border: 1px solid var(--node-border-color);
|
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
@ -27,21 +26,7 @@
|
||||||
color: var(--node-text-color);
|
color: var(--node-text-color);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
border: 1px solid var(--node-border-color);
|
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.isEditable {
|
|
||||||
padding: 12px;
|
|
||||||
|
|
||||||
.itemWrap {
|
|
||||||
margin-top: 12px;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty {
|
|
||||||
margin-top: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,74 +2,53 @@ import { NodeViewWrapper } from '@tiptap/react';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import cls from 'classnames';
|
import cls from 'classnames';
|
||||||
import { Select } from '@douyinfe/semi-ui';
|
|
||||||
import { useWikiTocs } from 'data/wiki';
|
|
||||||
import { DataRender } from 'components/data-render';
|
|
||||||
import { IconDocument } from 'components/icons';
|
import { IconDocument } from 'components/icons';
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
export const DocumentReferenceWrapper = ({ editor, node, updateAttributes }) => {
|
export const DocumentReferenceWrapper = ({ editor, node, updateAttributes }) => {
|
||||||
const { pathname, query } = useRouter();
|
const { pathname } = useRouter();
|
||||||
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 { data: tocs, loading, error } = useWikiTocs(isShare ? null : wikiIdFromUrl);
|
|
||||||
|
|
||||||
const selectDoc = (str) => {
|
const content = useMemo(() => {
|
||||||
const [wikiId, title, documentId] = str.split('/');
|
if (!wikiId && !documentId) {
|
||||||
updateAttributes({ wikiId, documentId, title });
|
return (
|
||||||
};
|
<div className={cls(styles.empty, !isEditable && 'render-wrapper')}>
|
||||||
|
<span>{'用户未选择文档'}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isEditable) {
|
||||||
|
return (
|
||||||
|
<div className={cls(styles.itemWrap)}>
|
||||||
|
<IconDocument />
|
||||||
|
<span>{title}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
key={documentId}
|
||||||
|
href={{
|
||||||
|
pathname: `${!isShare ? '' : '/share'}/wiki/[wikiId]/document/[documentId]`,
|
||||||
|
query: { wikiId, documentId },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<a className={cls(styles.itemWrap, !isEditable && 'render-wrapper')} target="_blank">
|
||||||
|
<IconDocument />
|
||||||
|
<span>{title || '请选择文档'}</span>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}, [wikiId, documentId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NodeViewWrapper
|
<NodeViewWrapper as="div" className={cls(styles.wrap, isEditable && 'render-wrapper')}>
|
||||||
as="div"
|
{content}
|
||||||
className={cls(styles.wrap, isEditable && styles.isEditable, isEditable && 'render-wrapper')}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
{isEditable && (
|
|
||||||
<DataRender
|
|
||||||
loading={loading}
|
|
||||||
error={error}
|
|
||||||
normalContent={() => (
|
|
||||||
<Select
|
|
||||||
placeholder="请选择文档"
|
|
||||||
onChange={(v) => selectDoc(v)}
|
|
||||||
style={{ maxWidth: 180 }}
|
|
||||||
{...(wikiId && documentId ? { value: `${wikiId}/${title}/${documentId}` } : {})}
|
|
||||||
>
|
|
||||||
{(tocs || []).map((toc) => (
|
|
||||||
<Select.Option
|
|
||||||
// FIXME: semi-design 抄 antd,抄的什么玩意!!!
|
|
||||||
label={`${toc.title}/${toc.id}`}
|
|
||||||
value={`${toc.wikiId}/${toc.title}/${toc.id}`}
|
|
||||||
>
|
|
||||||
{toc.title}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{wikiId && documentId ? (
|
|
||||||
<Link
|
|
||||||
key={documentId}
|
|
||||||
href={{
|
|
||||||
pathname: `${!isShare ? '' : '/share'}/wiki/[wikiId]/document/[documentId]`,
|
|
||||||
query: { wikiId, documentId },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<a className={cls(styles.itemWrap, !isEditable && 'render-wrapper')} target="_blank">
|
|
||||||
<IconDocument />
|
|
||||||
<span>{title || '请选择文档'}</span>
|
|
||||||
</a>
|
|
||||||
</Link>
|
|
||||||
) : (
|
|
||||||
<div className={cls(styles.empty, !isEditable && 'render-wrapper')}>
|
|
||||||
<span>{'用户未选择文档'}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</NodeViewWrapper>
|
</NodeViewWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue