@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.
Files changed (33) hide show
  1. package/package.json +1 -1
  2. package/template/client/src/components/ui/markdown.tsx +43 -0
  3. package/template/client/src/index.css +0 -1
  4. package/template/nest-cli.json +10 -9
  5. package/template/package-lock.json +10339 -18979
  6. package/template/package.json +7 -70
  7. package/template/scripts/build.sh +12 -0
  8. package/template/vite.config.ts +1 -1
  9. package/template/client/src/components/business-ui/tiptap-editor/README.md +0 -324
  10. package/template/client/src/components/business-ui/tiptap-editor/components/attachment-toolbar-button.tsx +0 -74
  11. package/template/client/src/components/business-ui/tiptap-editor/components/blockquote-toolbar-button.tsx +0 -41
  12. package/template/client/src/components/business-ui/tiptap-editor/components/code-block-toolbar-button.tsx +0 -41
  13. package/template/client/src/components/business-ui/tiptap-editor/components/color-highlight-toolbar-button.tsx +0 -141
  14. package/template/client/src/components/business-ui/tiptap-editor/components/heading-toolbar-button.tsx +0 -99
  15. package/template/client/src/components/business-ui/tiptap-editor/components/horizontal-rule-toolbar-button.tsx +0 -39
  16. package/template/client/src/components/business-ui/tiptap-editor/components/image-upload-toolbar-button.tsx +0 -65
  17. package/template/client/src/components/business-ui/tiptap-editor/components/link-edit-form.tsx +0 -127
  18. package/template/client/src/components/business-ui/tiptap-editor/components/link-hover-toolbar.tsx +0 -380
  19. package/template/client/src/components/business-ui/tiptap-editor/components/link-toolbar-button.tsx +0 -96
  20. package/template/client/src/components/business-ui/tiptap-editor/components/list-toolbar-button.tsx +0 -75
  21. package/template/client/src/components/business-ui/tiptap-editor/components/mark-toolbar-button.tsx +0 -118
  22. package/template/client/src/components/business-ui/tiptap-editor/components/text-align-toolbar-button.tsx +0 -84
  23. package/template/client/src/components/business-ui/tiptap-editor/components/undo-redo-toolbar-button.tsx +0 -54
  24. package/template/client/src/components/business-ui/tiptap-editor/extensions/attachment.tsx +0 -539
  25. package/template/client/src/components/business-ui/tiptap-editor/extensions/code-block-shiki.tsx +0 -236
  26. package/template/client/src/components/business-ui/tiptap-editor/extensions/complete-kit.ts +0 -260
  27. package/template/client/src/components/business-ui/tiptap-editor/extensions/image.tsx +0 -456
  28. package/template/client/src/components/business-ui/tiptap-editor/hooks/use-tiptap-editor.ts +0 -47
  29. package/template/client/src/components/business-ui/tiptap-editor/index.ts +0 -38
  30. package/template/client/src/components/business-ui/tiptap-editor/tiptap-editor-complete.tsx +0 -112
  31. package/template/client/src/components/business-ui/tiptap-editor/tiptap-editor.tsx +0 -169
  32. package/template/client/src/components/ui/streamdown.tsx +0 -186
  33. 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
- }
@@ -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
- }