@lark-apaas/coding-template-nestjs-react-fullstack 0.1.35 → 0.1.36-alpha.20260901035538
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/template/client/src/components/ui/markdown.tsx +43 -0
- package/template/client/src/index.css +0 -1
- package/template/nest-cli.json +10 -9
- package/template/package-lock.json +10339 -18979
- package/template/package.json +7 -70
- package/template/scripts/build.sh +12 -0
- package/template/vite.config.ts +1 -1
- package/template/client/src/components/business-ui/tiptap-editor/README.md +0 -324
- package/template/client/src/components/business-ui/tiptap-editor/components/attachment-toolbar-button.tsx +0 -74
- package/template/client/src/components/business-ui/tiptap-editor/components/blockquote-toolbar-button.tsx +0 -41
- package/template/client/src/components/business-ui/tiptap-editor/components/code-block-toolbar-button.tsx +0 -41
- package/template/client/src/components/business-ui/tiptap-editor/components/color-highlight-toolbar-button.tsx +0 -141
- package/template/client/src/components/business-ui/tiptap-editor/components/heading-toolbar-button.tsx +0 -99
- package/template/client/src/components/business-ui/tiptap-editor/components/horizontal-rule-toolbar-button.tsx +0 -39
- package/template/client/src/components/business-ui/tiptap-editor/components/image-upload-toolbar-button.tsx +0 -65
- package/template/client/src/components/business-ui/tiptap-editor/components/link-edit-form.tsx +0 -127
- package/template/client/src/components/business-ui/tiptap-editor/components/link-hover-toolbar.tsx +0 -380
- package/template/client/src/components/business-ui/tiptap-editor/components/link-toolbar-button.tsx +0 -96
- package/template/client/src/components/business-ui/tiptap-editor/components/list-toolbar-button.tsx +0 -75
- package/template/client/src/components/business-ui/tiptap-editor/components/mark-toolbar-button.tsx +0 -118
- package/template/client/src/components/business-ui/tiptap-editor/components/text-align-toolbar-button.tsx +0 -84
- package/template/client/src/components/business-ui/tiptap-editor/components/undo-redo-toolbar-button.tsx +0 -54
- package/template/client/src/components/business-ui/tiptap-editor/extensions/attachment.tsx +0 -539
- package/template/client/src/components/business-ui/tiptap-editor/extensions/code-block-shiki.tsx +0 -236
- package/template/client/src/components/business-ui/tiptap-editor/extensions/complete-kit.ts +0 -260
- package/template/client/src/components/business-ui/tiptap-editor/extensions/image.tsx +0 -456
- package/template/client/src/components/business-ui/tiptap-editor/hooks/use-tiptap-editor.ts +0 -47
- package/template/client/src/components/business-ui/tiptap-editor/index.ts +0 -38
- package/template/client/src/components/business-ui/tiptap-editor/tiptap-editor-complete.tsx +0 -112
- package/template/client/src/components/business-ui/tiptap-editor/tiptap-editor.tsx +0 -169
- package/template/client/src/components/ui/streamdown.tsx +0 -186
- package/template/client/src/lib/shiki.ts +0 -92
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import * as React from 'react';
|
|
4
|
-
import { Baseline, Check, ChevronDown } from 'lucide-react';
|
|
5
|
-
|
|
6
|
-
import { useTiptapEditor } from '@/components/business-ui/tiptap-editor/hooks/use-tiptap-editor';
|
|
7
|
-
import { cn } from '@/lib/utils';
|
|
8
|
-
import { Button } from '@/components/ui/button';
|
|
9
|
-
import {
|
|
10
|
-
Popover,
|
|
11
|
-
PopoverContent,
|
|
12
|
-
PopoverTrigger,
|
|
13
|
-
} from '@/components/ui/popover';
|
|
14
|
-
import {
|
|
15
|
-
Tooltip,
|
|
16
|
-
TooltipContent,
|
|
17
|
-
TooltipProvider,
|
|
18
|
-
TooltipTrigger,
|
|
19
|
-
} from '@/components/ui/tooltip';
|
|
20
|
-
|
|
21
|
-
// Design system colors matching Figma
|
|
22
|
-
const TEXT_COLORS = [
|
|
23
|
-
{ value: 'var(--foreground)', label: '默认', className: 'bg-foreground' },
|
|
24
|
-
{
|
|
25
|
-
value: 'var(--muted-foreground)',
|
|
26
|
-
label: '次要',
|
|
27
|
-
className: 'bg-muted-foreground',
|
|
28
|
-
},
|
|
29
|
-
{ value: 'var(--color-red-600)', label: '红色', className: 'bg-red-600' },
|
|
30
|
-
{
|
|
31
|
-
value: 'var(--color-orange-500)',
|
|
32
|
-
label: '橙色',
|
|
33
|
-
className: 'bg-orange-500',
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
value: 'var(--color-yellow-500)',
|
|
37
|
-
label: '黄色',
|
|
38
|
-
className: 'bg-yellow-500',
|
|
39
|
-
},
|
|
40
|
-
{ value: 'var(--color-green-500)', label: '绿色', className: 'bg-green-500' },
|
|
41
|
-
{ value: 'var(--color-blue-600)', label: '蓝色', className: 'bg-blue-600' },
|
|
42
|
-
{
|
|
43
|
-
value: 'var(--color-purple-600)',
|
|
44
|
-
label: '紫色',
|
|
45
|
-
className: 'bg-purple-600',
|
|
46
|
-
},
|
|
47
|
-
];
|
|
48
|
-
|
|
49
|
-
export function ColorHighlightToolbarButton() {
|
|
50
|
-
const { editor } = useTiptapEditor();
|
|
51
|
-
const [open, setOpen] = React.useState(false);
|
|
52
|
-
|
|
53
|
-
if (!editor) return null;
|
|
54
|
-
|
|
55
|
-
const currentColor =
|
|
56
|
-
editor.getAttributes('textStyle').color || 'var(--foreground)';
|
|
57
|
-
|
|
58
|
-
const disabled = !editor.can().setColor(TEXT_COLORS[0]!.value);
|
|
59
|
-
|
|
60
|
-
const handleColorSelect = (color: string) => {
|
|
61
|
-
if (color === 'var(--foreground)') {
|
|
62
|
-
editor.chain().focus().unsetColor().run();
|
|
63
|
-
} else {
|
|
64
|
-
editor.chain().focus().setColor(color).run();
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const handleResetColor = () => {
|
|
69
|
-
editor.chain().focus().unsetColor().run();
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
const trigger = (
|
|
73
|
-
<Button
|
|
74
|
-
variant="ghost"
|
|
75
|
-
size="sm"
|
|
76
|
-
className="h-6 gap-0.5 px-2"
|
|
77
|
-
disabled={disabled}
|
|
78
|
-
>
|
|
79
|
-
<Baseline className="size-4" style={{ color: currentColor }} />
|
|
80
|
-
<ChevronDown className="size-3.5 text-muted-foreground" />
|
|
81
|
-
<span className="sr-only">文字颜色</span>
|
|
82
|
-
</Button>
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
return (
|
|
86
|
-
<Popover open={open} onOpenChange={setOpen}>
|
|
87
|
-
<TooltipProvider>
|
|
88
|
-
<Tooltip delayDuration={700}>
|
|
89
|
-
<TooltipTrigger asChild>
|
|
90
|
-
<PopoverTrigger asChild>{trigger}</PopoverTrigger>
|
|
91
|
-
</TooltipTrigger>
|
|
92
|
-
{!open && (
|
|
93
|
-
<TooltipContent>
|
|
94
|
-
<p>文字颜色</p>
|
|
95
|
-
</TooltipContent>
|
|
96
|
-
)}
|
|
97
|
-
</Tooltip>
|
|
98
|
-
</TooltipProvider>
|
|
99
|
-
<PopoverContent className="w-auto p-4" align="start">
|
|
100
|
-
<div className="flex flex-col items-center gap-3">
|
|
101
|
-
{/* Color grid */}
|
|
102
|
-
<div className="flex gap-1">
|
|
103
|
-
{TEXT_COLORS.map((color) => {
|
|
104
|
-
const isSelected = currentColor === color.value;
|
|
105
|
-
|
|
106
|
-
return (
|
|
107
|
-
<button
|
|
108
|
-
key={color.value}
|
|
109
|
-
className={cn(
|
|
110
|
-
'relative flex size-6 items-center justify-center rounded-md p-0.5 transition-colors',
|
|
111
|
-
'hover:ring-1 hover:ring-ring/20',
|
|
112
|
-
isSelected && 'ring-1 ring-ring',
|
|
113
|
-
)}
|
|
114
|
-
onClick={() => handleColorSelect(color.value)}
|
|
115
|
-
title={color.label}
|
|
116
|
-
>
|
|
117
|
-
<span
|
|
118
|
-
className={cn('size-5 rounded-[5px]', color.className)}
|
|
119
|
-
/>
|
|
120
|
-
{isSelected && (
|
|
121
|
-
<Check className="absolute size-3.5 text-white" />
|
|
122
|
-
)}
|
|
123
|
-
</button>
|
|
124
|
-
);
|
|
125
|
-
})}
|
|
126
|
-
</div>
|
|
127
|
-
|
|
128
|
-
{/* Reset button */}
|
|
129
|
-
<Button
|
|
130
|
-
variant="outline"
|
|
131
|
-
size="sm"
|
|
132
|
-
className="w-full"
|
|
133
|
-
onClick={handleResetColor}
|
|
134
|
-
>
|
|
135
|
-
恢复默认
|
|
136
|
-
</Button>
|
|
137
|
-
</div>
|
|
138
|
-
</PopoverContent>
|
|
139
|
-
</Popover>
|
|
140
|
-
);
|
|
141
|
-
}
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
Check,
|
|
5
|
-
ChevronDown,
|
|
6
|
-
Heading1,
|
|
7
|
-
Heading2,
|
|
8
|
-
Heading3,
|
|
9
|
-
Heading4,
|
|
10
|
-
Heading5,
|
|
11
|
-
Heading6,
|
|
12
|
-
Type,
|
|
13
|
-
} from 'lucide-react';
|
|
14
|
-
|
|
15
|
-
import { useTiptapEditor } from '@/components/business-ui/tiptap-editor/hooks/use-tiptap-editor';
|
|
16
|
-
import { cn } from '@/lib/utils';
|
|
17
|
-
import { Button } from '@/components/ui/button';
|
|
18
|
-
import {
|
|
19
|
-
DropdownMenu,
|
|
20
|
-
DropdownMenuContent,
|
|
21
|
-
DropdownMenuItem,
|
|
22
|
-
DropdownMenuTrigger,
|
|
23
|
-
} from '@/components/ui/dropdown-menu';
|
|
24
|
-
|
|
25
|
-
const HEADING_OPTIONS = [
|
|
26
|
-
{ level: 0, label: '正文', icon: Type },
|
|
27
|
-
{ level: 1, label: '一级标题', icon: Heading1 },
|
|
28
|
-
{ level: 2, label: '二级标题', icon: Heading2 },
|
|
29
|
-
{ level: 3, label: '三级标题', icon: Heading3 },
|
|
30
|
-
{ level: 4, label: '四级标题', icon: Heading4 },
|
|
31
|
-
{ level: 5, label: '五级标题', icon: Heading5 },
|
|
32
|
-
{ level: 6, label: '六级标题', icon: Heading6 },
|
|
33
|
-
] as const;
|
|
34
|
-
|
|
35
|
-
export function HeadingToolbarButton() {
|
|
36
|
-
const { editor } = useTiptapEditor();
|
|
37
|
-
|
|
38
|
-
if (!editor) return null;
|
|
39
|
-
|
|
40
|
-
const handleSelect = (level: number) => {
|
|
41
|
-
if (level === 0) {
|
|
42
|
-
editor.chain().focus().setParagraph().run();
|
|
43
|
-
} else {
|
|
44
|
-
editor
|
|
45
|
-
.chain()
|
|
46
|
-
.focus()
|
|
47
|
-
.toggleHeading({ level: level as 1 | 2 | 3 | 4 | 5 | 6 })
|
|
48
|
-
.run();
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
const isActive = (level: number) => {
|
|
53
|
-
if (level === 0) return editor.isActive('paragraph');
|
|
54
|
-
return editor.isActive('heading', { level });
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
const activeOption =
|
|
58
|
-
HEADING_OPTIONS.find((option) => isActive(option.level)) ||
|
|
59
|
-
HEADING_OPTIONS[0];
|
|
60
|
-
const ActiveIcon = activeOption.icon;
|
|
61
|
-
|
|
62
|
-
return (
|
|
63
|
-
<DropdownMenu>
|
|
64
|
-
<DropdownMenuTrigger asChild>
|
|
65
|
-
<Button variant="ghost" size="sm" className="h-6 gap-0.5 px-2">
|
|
66
|
-
<ActiveIcon className="size-4" />
|
|
67
|
-
<ChevronDown className="size-3.5 text-muted-foreground" />
|
|
68
|
-
</Button>
|
|
69
|
-
</DropdownMenuTrigger>
|
|
70
|
-
<DropdownMenuContent align="start" className="w-50">
|
|
71
|
-
{HEADING_OPTIONS.map((option) => {
|
|
72
|
-
const Icon = option.icon;
|
|
73
|
-
const active = isActive(option.level);
|
|
74
|
-
const disabled =
|
|
75
|
-
option.level === 0
|
|
76
|
-
? !editor.can().setParagraph()
|
|
77
|
-
: !editor.can().toggleHeading({
|
|
78
|
-
level: option.level as 1 | 2 | 3 | 4 | 5 | 6,
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
return (
|
|
82
|
-
<DropdownMenuItem
|
|
83
|
-
key={option.level}
|
|
84
|
-
onClick={() => handleSelect(option.level)}
|
|
85
|
-
disabled={disabled}
|
|
86
|
-
className={cn('justify-between', active && 'bg-accent')}
|
|
87
|
-
>
|
|
88
|
-
<span className="flex items-center gap-2">
|
|
89
|
-
<Icon className="size-4" />
|
|
90
|
-
{option.label}
|
|
91
|
-
</span>
|
|
92
|
-
{active && <Check className="size-4" />}
|
|
93
|
-
</DropdownMenuItem>
|
|
94
|
-
);
|
|
95
|
-
})}
|
|
96
|
-
</DropdownMenuContent>
|
|
97
|
-
</DropdownMenu>
|
|
98
|
-
);
|
|
99
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { Minus } from 'lucide-react';
|
|
4
|
-
|
|
5
|
-
import { useTiptapEditor } from '@/components/business-ui/tiptap-editor/hooks/use-tiptap-editor';
|
|
6
|
-
import { Button } from '@/components/ui/button';
|
|
7
|
-
import {
|
|
8
|
-
Tooltip,
|
|
9
|
-
TooltipContent,
|
|
10
|
-
TooltipProvider,
|
|
11
|
-
TooltipTrigger,
|
|
12
|
-
} from '@/components/ui/tooltip';
|
|
13
|
-
|
|
14
|
-
export function HorizontalRuleToolbarButton() {
|
|
15
|
-
const { editor } = useTiptapEditor();
|
|
16
|
-
if (!editor) return null;
|
|
17
|
-
|
|
18
|
-
return (
|
|
19
|
-
<TooltipProvider>
|
|
20
|
-
<Tooltip delayDuration={700}>
|
|
21
|
-
<TooltipTrigger asChild>
|
|
22
|
-
<Button
|
|
23
|
-
variant="ghost"
|
|
24
|
-
size="sm"
|
|
25
|
-
onClick={() => editor.chain().focus().setHorizontalRule().run()}
|
|
26
|
-
disabled={!editor.can().setHorizontalRule()}
|
|
27
|
-
aria-label="分割线"
|
|
28
|
-
className="size-6 px-0"
|
|
29
|
-
>
|
|
30
|
-
<Minus className="size-4" />
|
|
31
|
-
</Button>
|
|
32
|
-
</TooltipTrigger>
|
|
33
|
-
<TooltipContent>
|
|
34
|
-
<p>分割线(⌘+Option+S)</p>
|
|
35
|
-
</TooltipContent>
|
|
36
|
-
</Tooltip>
|
|
37
|
-
</TooltipProvider>
|
|
38
|
-
);
|
|
39
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import * as React from 'react';
|
|
4
|
-
import { Image as ImageIcon } from 'lucide-react';
|
|
5
|
-
import { toast } from 'sonner';
|
|
6
|
-
|
|
7
|
-
import { useTiptapEditor } from '@/components/business-ui/tiptap-editor/hooks/use-tiptap-editor';
|
|
8
|
-
import { Button } from '@/components/ui/button';
|
|
9
|
-
import {
|
|
10
|
-
Tooltip,
|
|
11
|
-
TooltipContent,
|
|
12
|
-
TooltipProvider,
|
|
13
|
-
TooltipTrigger,
|
|
14
|
-
} from '@/components/ui/tooltip';
|
|
15
|
-
|
|
16
|
-
export function ImageUploadToolbarButton() {
|
|
17
|
-
const { editor } = useTiptapEditor();
|
|
18
|
-
const fileInputRef = React.useRef<HTMLInputElement>(null);
|
|
19
|
-
|
|
20
|
-
if (!editor) return null;
|
|
21
|
-
|
|
22
|
-
const handleFileChange = async (
|
|
23
|
-
event: React.ChangeEvent<HTMLInputElement>,
|
|
24
|
-
) => {
|
|
25
|
-
const file = event.target.files?.[0];
|
|
26
|
-
if (!file) return;
|
|
27
|
-
|
|
28
|
-
const ok = editor.chain().focus().insertImages([file]).run();
|
|
29
|
-
if (!ok) {
|
|
30
|
-
toast.error('插入图片失败(请确认图片扩展已启用且提供 upload)');
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (fileInputRef.current) {
|
|
34
|
-
fileInputRef.current.value = '';
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
return (
|
|
39
|
-
<TooltipProvider>
|
|
40
|
-
<Tooltip delayDuration={700}>
|
|
41
|
-
<TooltipTrigger asChild>
|
|
42
|
-
<Button
|
|
43
|
-
variant="ghost"
|
|
44
|
-
size="sm"
|
|
45
|
-
className="size-6 px-0"
|
|
46
|
-
onClick={() => fileInputRef.current?.click()}
|
|
47
|
-
>
|
|
48
|
-
<ImageIcon className="size-4" />
|
|
49
|
-
<span className="sr-only">上传图片</span>
|
|
50
|
-
</Button>
|
|
51
|
-
</TooltipTrigger>
|
|
52
|
-
<TooltipContent>
|
|
53
|
-
<p>上传图片</p>
|
|
54
|
-
</TooltipContent>
|
|
55
|
-
</Tooltip>
|
|
56
|
-
<input
|
|
57
|
-
type="file"
|
|
58
|
-
ref={fileInputRef}
|
|
59
|
-
className="hidden"
|
|
60
|
-
accept="image/*"
|
|
61
|
-
onChange={handleFileChange}
|
|
62
|
-
/>
|
|
63
|
-
</TooltipProvider>
|
|
64
|
-
);
|
|
65
|
-
}
|
package/template/client/src/components/business-ui/tiptap-editor/components/link-edit-form.tsx
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import * as React from 'react';
|
|
4
|
-
|
|
5
|
-
import { useTiptapEditor } from '@/components/business-ui/tiptap-editor/hooks/use-tiptap-editor';
|
|
6
|
-
import { cn } from '@/lib/utils';
|
|
7
|
-
import { Button } from '@/components/ui/button';
|
|
8
|
-
import { Input } from '@/components/ui/input';
|
|
9
|
-
import { Label } from '@/components/ui/label';
|
|
10
|
-
|
|
11
|
-
export interface LinkEditFormProps extends React.ComponentProps<'div'> {
|
|
12
|
-
open: boolean;
|
|
13
|
-
initialText?: string;
|
|
14
|
-
initialHref?: string;
|
|
15
|
-
/** 打开时自动聚焦到链接输入框。 */
|
|
16
|
-
autoFocusHref?: boolean;
|
|
17
|
-
onDone?: () => void;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function LinkEditForm({
|
|
21
|
-
open,
|
|
22
|
-
initialText = '',
|
|
23
|
-
initialHref = '',
|
|
24
|
-
autoFocusHref = false,
|
|
25
|
-
onDone,
|
|
26
|
-
className,
|
|
27
|
-
...props
|
|
28
|
-
}: LinkEditFormProps) {
|
|
29
|
-
const { editor } = useTiptapEditor();
|
|
30
|
-
const [text, setText] = React.useState('');
|
|
31
|
-
const [href, setHref] = React.useState('');
|
|
32
|
-
|
|
33
|
-
const hrefInputRef = React.useRef<HTMLInputElement | null>(null);
|
|
34
|
-
|
|
35
|
-
const textId = React.useId();
|
|
36
|
-
const hrefId = React.useId();
|
|
37
|
-
|
|
38
|
-
React.useEffect(() => {
|
|
39
|
-
if (!open) return;
|
|
40
|
-
setText(initialText);
|
|
41
|
-
setHref(initialHref);
|
|
42
|
-
}, [open, initialHref, initialText]);
|
|
43
|
-
|
|
44
|
-
React.useEffect(() => {
|
|
45
|
-
if (!open) return;
|
|
46
|
-
if (!autoFocusHref) return;
|
|
47
|
-
|
|
48
|
-
const id = window.setTimeout(() => {
|
|
49
|
-
hrefInputRef.current?.focus();
|
|
50
|
-
}, 0);
|
|
51
|
-
|
|
52
|
-
return () => window.clearTimeout(id);
|
|
53
|
-
}, [autoFocusHref, open]);
|
|
54
|
-
|
|
55
|
-
if (!editor) return null;
|
|
56
|
-
|
|
57
|
-
const isInLink = editor.isActive('link');
|
|
58
|
-
const selectionEmpty = editor.state.selection.empty;
|
|
59
|
-
|
|
60
|
-
const hrefTrimmed = href.trim();
|
|
61
|
-
const textTrimmed = text.trim();
|
|
62
|
-
|
|
63
|
-
const canSubmit =
|
|
64
|
-
hrefTrimmed.length > 0 &&
|
|
65
|
-
(!selectionEmpty || isInLink || textTrimmed.length);
|
|
66
|
-
|
|
67
|
-
const applyLink = () => {
|
|
68
|
-
if (!canSubmit) return;
|
|
69
|
-
|
|
70
|
-
if (textTrimmed.length > 0) {
|
|
71
|
-
if (isInLink && editor.state.selection.empty) {
|
|
72
|
-
editor.chain().focus().extendMarkRange('link').run();
|
|
73
|
-
} else {
|
|
74
|
-
editor.chain().focus().run();
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
editor.commands.insertContent({
|
|
78
|
-
type: 'text',
|
|
79
|
-
text: textTrimmed,
|
|
80
|
-
marks: [{ type: 'link', attrs: { href: hrefTrimmed } }],
|
|
81
|
-
});
|
|
82
|
-
} else {
|
|
83
|
-
const chain = editor.chain().focus();
|
|
84
|
-
if (isInLink) chain.extendMarkRange('link');
|
|
85
|
-
chain.setLink({ href: hrefTrimmed }).run();
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
onDone?.();
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
return (
|
|
92
|
-
<div className={cn('flex flex-col gap-4', className)} {...props}>
|
|
93
|
-
<div className="flex items-center gap-4">
|
|
94
|
-
<Label htmlFor={textId} className="w-10 shrink-0">
|
|
95
|
-
文本
|
|
96
|
-
</Label>
|
|
97
|
-
<Input
|
|
98
|
-
id={textId}
|
|
99
|
-
className="h-8"
|
|
100
|
-
placeholder="请输入文本"
|
|
101
|
-
value={text}
|
|
102
|
-
onChange={(e) => setText(e.target.value)}
|
|
103
|
-
/>
|
|
104
|
-
</div>
|
|
105
|
-
|
|
106
|
-
<div className="flex items-center gap-4">
|
|
107
|
-
<Label htmlFor={hrefId} className="w-10 shrink-0">
|
|
108
|
-
链接
|
|
109
|
-
</Label>
|
|
110
|
-
<Input
|
|
111
|
-
id={hrefId}
|
|
112
|
-
className="h-8"
|
|
113
|
-
placeholder="粘贴或输入链接"
|
|
114
|
-
value={href}
|
|
115
|
-
onChange={(e) => setHref(e.target.value)}
|
|
116
|
-
ref={hrefInputRef}
|
|
117
|
-
/>
|
|
118
|
-
</div>
|
|
119
|
-
|
|
120
|
-
<div className="flex justify-end">
|
|
121
|
-
<Button size="sm" disabled={!canSubmit} onClick={applyLink}>
|
|
122
|
-
确定
|
|
123
|
-
</Button>
|
|
124
|
-
</div>
|
|
125
|
-
</div>
|
|
126
|
-
);
|
|
127
|
-
}
|