@lark-apaas/coding-template-nestjs-react-fullstack 0.1.34-alpha.20260831015214 → 0.1.34

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/business-ui/tiptap-editor/README.md +324 -0
  3. package/template/client/src/components/business-ui/tiptap-editor/components/attachment-toolbar-button.tsx +74 -0
  4. package/template/client/src/components/business-ui/tiptap-editor/components/blockquote-toolbar-button.tsx +41 -0
  5. package/template/client/src/components/business-ui/tiptap-editor/components/code-block-toolbar-button.tsx +41 -0
  6. package/template/client/src/components/business-ui/tiptap-editor/components/color-highlight-toolbar-button.tsx +141 -0
  7. package/template/client/src/components/business-ui/tiptap-editor/components/heading-toolbar-button.tsx +99 -0
  8. package/template/client/src/components/business-ui/tiptap-editor/components/horizontal-rule-toolbar-button.tsx +39 -0
  9. package/template/client/src/components/business-ui/tiptap-editor/components/image-upload-toolbar-button.tsx +65 -0
  10. package/template/client/src/components/business-ui/tiptap-editor/components/link-edit-form.tsx +127 -0
  11. package/template/client/src/components/business-ui/tiptap-editor/components/link-hover-toolbar.tsx +380 -0
  12. package/template/client/src/components/business-ui/tiptap-editor/components/link-toolbar-button.tsx +96 -0
  13. package/template/client/src/components/business-ui/tiptap-editor/components/list-toolbar-button.tsx +75 -0
  14. package/template/client/src/components/business-ui/tiptap-editor/components/mark-toolbar-button.tsx +118 -0
  15. package/template/client/src/components/business-ui/tiptap-editor/components/text-align-toolbar-button.tsx +84 -0
  16. package/template/client/src/components/business-ui/tiptap-editor/components/undo-redo-toolbar-button.tsx +54 -0
  17. package/template/client/src/components/business-ui/tiptap-editor/extensions/attachment.tsx +539 -0
  18. package/template/client/src/components/business-ui/tiptap-editor/extensions/code-block-shiki.tsx +236 -0
  19. package/template/client/src/components/business-ui/tiptap-editor/extensions/complete-kit.ts +260 -0
  20. package/template/client/src/components/business-ui/tiptap-editor/extensions/image.tsx +456 -0
  21. package/template/client/src/components/business-ui/tiptap-editor/hooks/use-tiptap-editor.ts +47 -0
  22. package/template/client/src/components/business-ui/tiptap-editor/index.ts +38 -0
  23. package/template/client/src/components/business-ui/tiptap-editor/tiptap-editor-complete.tsx +112 -0
  24. package/template/client/src/components/business-ui/tiptap-editor/tiptap-editor.tsx +169 -0
  25. package/template/client/src/components/ui/streamdown.tsx +186 -0
  26. package/template/client/src/index.css +1 -0
  27. package/template/client/src/lib/shiki.ts +92 -0
  28. package/template/nest-cli.json +9 -10
  29. package/template/package-lock.json +11958 -6326
  30. package/template/package.json +51 -7
  31. package/template/scripts/build.sh +0 -12
  32. package/template/vite.config.ts +1 -1
  33. package/template/client/src/components/ui/markdown.tsx +0 -43
@@ -0,0 +1,96 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { Link as LinkIcon } from 'lucide-react';
5
+
6
+ import { LinkEditForm } from '@/components/business-ui/tiptap-editor/components/link-edit-form';
7
+ import { useTiptapEditor } from '@/components/business-ui/tiptap-editor/hooks/use-tiptap-editor';
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
+ export function LinkToolbarButton() {
22
+ const { editor } = useTiptapEditor();
23
+ const [open, setOpen] = React.useState(false);
24
+ const [initialText, setInitialText] = React.useState('');
25
+ const [initialHref, setInitialHref] = React.useState('');
26
+
27
+ if (!editor) return null;
28
+
29
+ const resetForm = () => {
30
+ setInitialText('');
31
+ setInitialHref('');
32
+ };
33
+
34
+ const openDialog = () => {
35
+ const { from, to } = editor.state.selection;
36
+ const selectedText = editor.state.doc.textBetween(from, to, ' ');
37
+ const currentHref = (editor.getAttributes('link')?.href as string) || '';
38
+
39
+ setInitialText(selectedText);
40
+ setInitialHref(currentHref);
41
+ setOpen(true);
42
+ };
43
+
44
+ const trigger = (
45
+ <Button
46
+ variant="ghost"
47
+ size="sm"
48
+ className="size-6 px-0"
49
+ onClick={openDialog}
50
+ disabled={!editor.can().toggleLink({ href: '' })}
51
+ >
52
+ <LinkIcon className="size-4" />
53
+ <span className="sr-only">链接</span>
54
+ </Button>
55
+ );
56
+
57
+ return (
58
+ <Popover
59
+ open={open}
60
+ onOpenChange={(nextOpen) => {
61
+ setOpen(nextOpen);
62
+ if (!nextOpen) resetForm();
63
+ }}
64
+ >
65
+ <TooltipProvider>
66
+ <Tooltip delayDuration={700}>
67
+ <TooltipTrigger asChild>
68
+ <PopoverTrigger asChild>{trigger}</PopoverTrigger>
69
+ </TooltipTrigger>
70
+ {!open && (
71
+ <TooltipContent>
72
+ <p>链接</p>
73
+ </TooltipContent>
74
+ )}
75
+ </Tooltip>
76
+ </TooltipProvider>
77
+
78
+ <PopoverContent
79
+ className="w-105 p-5 shadow-lg"
80
+ align="start"
81
+ sideOffset={6}
82
+ >
83
+ <LinkEditForm
84
+ open={open}
85
+ initialText={initialText}
86
+ initialHref={initialHref}
87
+ autoFocusHref
88
+ onDone={() => {
89
+ setOpen(false);
90
+ resetForm();
91
+ }}
92
+ />
93
+ </PopoverContent>
94
+ </Popover>
95
+ );
96
+ }
@@ -0,0 +1,75 @@
1
+ 'use client';
2
+
3
+ import { Check, ChevronDown, List, ListOrdered, ListTodo } from 'lucide-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 {
9
+ DropdownMenu,
10
+ DropdownMenuContent,
11
+ DropdownMenuItem,
12
+ DropdownMenuTrigger,
13
+ } from '@/components/ui/dropdown-menu';
14
+
15
+ const LIST_OPTIONS = [
16
+ { type: 'bulletList', label: '无序列表', icon: List },
17
+ { type: 'orderedList', label: '有序列表', icon: ListOrdered },
18
+ { type: 'taskList', label: '任务列表', icon: ListTodo },
19
+ ] as const;
20
+
21
+ export function ListToolbarButton() {
22
+ const { editor } = useTiptapEditor();
23
+
24
+ if (!editor) return null;
25
+
26
+ const handleSelect = (type: string) => {
27
+ switch (type) {
28
+ case 'bulletList':
29
+ editor.chain().focus().toggleBulletList().run();
30
+ break;
31
+ case 'orderedList':
32
+ editor.chain().focus().toggleOrderedList().run();
33
+ break;
34
+ case 'taskList':
35
+ editor.chain().focus().toggleTaskList().run();
36
+ break;
37
+ }
38
+ };
39
+
40
+ return (
41
+ <DropdownMenu>
42
+ <DropdownMenuTrigger asChild>
43
+ <Button variant="ghost" size="sm" className="h-6 gap-0.5 px-2">
44
+ <List className="size-4" />
45
+ <ChevronDown className="size-3.5 text-muted-foreground" />
46
+ <span className="sr-only">列表</span>
47
+ </Button>
48
+ </DropdownMenuTrigger>
49
+ <DropdownMenuContent
50
+ align="start"
51
+ className="w-50"
52
+ onCloseAutoFocus={(e) => e.preventDefault()}
53
+ >
54
+ {LIST_OPTIONS.map((option) => {
55
+ const Icon = option.icon;
56
+ const active = editor.isActive(option.type);
57
+
58
+ return (
59
+ <DropdownMenuItem
60
+ key={option.type}
61
+ onClick={() => handleSelect(option.type)}
62
+ className={cn('justify-between', active && 'bg-accent')}
63
+ >
64
+ <span className="flex items-center gap-2">
65
+ <Icon className="size-4" />
66
+ {option.label}
67
+ </span>
68
+ {active && <Check className="size-4" />}
69
+ </DropdownMenuItem>
70
+ );
71
+ })}
72
+ </DropdownMenuContent>
73
+ </DropdownMenu>
74
+ );
75
+ }
@@ -0,0 +1,118 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { Bold, Code, Italic, Strikethrough, Underline } from 'lucide-react';
5
+
6
+ import { useTiptapEditor } from '@/components/business-ui/tiptap-editor/hooks/use-tiptap-editor';
7
+ import { Toggle } from '@/components/ui/toggle';
8
+ import {
9
+ Tooltip,
10
+ TooltipContent,
11
+ TooltipProvider,
12
+ TooltipTrigger,
13
+ } from '@/components/ui/tooltip';
14
+
15
+ interface MarkToolbarButtonProps {
16
+ format: 'bold' | 'italic' | 'underline' | 'strike' | 'code';
17
+ icon?: React.ReactNode;
18
+ tooltip?: string;
19
+ }
20
+
21
+ export function MarkToolbarButton({
22
+ format,
23
+ icon,
24
+ tooltip,
25
+ }: MarkToolbarButtonProps) {
26
+ const { editor } = useTiptapEditor();
27
+
28
+ if (!editor) return null;
29
+
30
+ const isActive = editor.isActive(format);
31
+
32
+ let disabled = false;
33
+
34
+ switch (format) {
35
+ case 'bold':
36
+ disabled = !editor.can().toggleBold();
37
+ break;
38
+ case 'italic':
39
+ disabled = !editor.can().toggleItalic();
40
+ break;
41
+ case 'underline':
42
+ disabled = !editor.can().toggleUnderline();
43
+ break;
44
+ case 'strike':
45
+ disabled = !editor.can().toggleStrike();
46
+ break;
47
+ case 'code':
48
+ disabled = !editor.can().toggleCode();
49
+ break;
50
+ }
51
+
52
+ const toggle = () => {
53
+ switch (format) {
54
+ case 'bold':
55
+ editor.chain().focus().toggleBold().run();
56
+ break;
57
+ case 'italic':
58
+ editor.chain().focus().toggleItalic().run();
59
+ break;
60
+ case 'underline':
61
+ editor.chain().focus().toggleUnderline().run();
62
+ break;
63
+ case 'strike':
64
+ editor.chain().focus().toggleStrike().run();
65
+ break;
66
+ case 'code':
67
+ editor.chain().focus().toggleCode().run();
68
+ break;
69
+ }
70
+ };
71
+
72
+ const icons = {
73
+ bold: <Bold className="size-4" />,
74
+ italic: <Italic className="size-4" />,
75
+ underline: <Underline className="size-4" />,
76
+ strike: <Strikethrough className="size-4" />,
77
+ code: <Code className="size-4" />,
78
+ };
79
+
80
+ const labels: Record<typeof format, string> = {
81
+ bold: '粗体',
82
+ italic: '斜体',
83
+ underline: '下划线',
84
+ strike: '删除线',
85
+ code: '行内代码',
86
+ };
87
+
88
+ const tooltips: Record<typeof format, string> = {
89
+ bold: '粗体(⌘+B)',
90
+ italic: '斜体(⌘+I)',
91
+ underline: '下划线(⌘+U)',
92
+ strike: '删除线(⌘+Shift+X)',
93
+ code: '行内代码(⌘+E)',
94
+ };
95
+
96
+ return (
97
+ <TooltipProvider>
98
+ <Tooltip delayDuration={700}>
99
+ <TooltipTrigger>
100
+ <Toggle
101
+ size="sm"
102
+ pressed={isActive}
103
+ onPressedChange={toggle}
104
+ disabled={disabled}
105
+ aria-label={labels[format]}
106
+ className="size-6 p-0"
107
+ asChild
108
+ >
109
+ <div>{icon || icons[format]}</div>
110
+ </Toggle>
111
+ </TooltipTrigger>
112
+ <TooltipContent>
113
+ <p>{tooltip || tooltips[format]}</p>
114
+ </TooltipContent>
115
+ </Tooltip>
116
+ </TooltipProvider>
117
+ );
118
+ }
@@ -0,0 +1,84 @@
1
+ 'use client';
2
+
3
+ import {
4
+ AlignCenter,
5
+ AlignLeft,
6
+ AlignRight,
7
+ Check,
8
+ ChevronDown,
9
+ } from 'lucide-react';
10
+
11
+ import { useTiptapEditor } from '@/components/business-ui/tiptap-editor/hooks/use-tiptap-editor';
12
+ import { cn } from '@/lib/utils';
13
+ import { Button } from '@/components/ui/button';
14
+ import {
15
+ DropdownMenu,
16
+ DropdownMenuContent,
17
+ DropdownMenuItem,
18
+ DropdownMenuTrigger,
19
+ } from '@/components/ui/dropdown-menu';
20
+
21
+ const ALIGN_OPTIONS = [
22
+ { value: 'left', label: '左对齐', icon: AlignLeft },
23
+ { value: 'center', label: '居中对齐', icon: AlignCenter },
24
+ { value: 'right', label: '右对齐', icon: AlignRight },
25
+ ] as const;
26
+
27
+ export function TextAlignToolbarButton() {
28
+ const { editor } = useTiptapEditor();
29
+
30
+ if (!editor) return null;
31
+
32
+ const getCurrentAlignment = () => {
33
+ if (editor.isActive({ textAlign: 'center' })) return 'center';
34
+ if (editor.isActive({ textAlign: 'right' })) return 'right';
35
+ return 'left';
36
+ };
37
+
38
+ const isDefaultLeft =
39
+ !editor.isActive({ textAlign: 'center' }) &&
40
+ !editor.isActive({ textAlign: 'right' }) &&
41
+ !editor.isActive({ textAlign: 'justify' });
42
+
43
+ const alignment = getCurrentAlignment();
44
+ const currentOption = ALIGN_OPTIONS.find((opt) => opt.value === alignment);
45
+ const Icon = currentOption?.icon ?? AlignLeft;
46
+
47
+ return (
48
+ <DropdownMenu>
49
+ <DropdownMenuTrigger asChild>
50
+ <Button variant="ghost" size="sm" className="h-6 gap-0.5 px-2">
51
+ <Icon className="size-4" />
52
+ <ChevronDown className="size-3.5 text-muted-foreground" />
53
+ <span className="sr-only">文字对齐</span>
54
+ </Button>
55
+ </DropdownMenuTrigger>
56
+ <DropdownMenuContent align="start" className="w-50">
57
+ {ALIGN_OPTIONS.map((option) => {
58
+ const OptionIcon = option.icon;
59
+ const active =
60
+ option.value === 'left'
61
+ ? editor.isActive({ textAlign: 'left' }) || isDefaultLeft
62
+ : editor.isActive({ textAlign: option.value });
63
+
64
+ return (
65
+ <DropdownMenuItem
66
+ key={option.value}
67
+ onClick={() =>
68
+ editor.chain().focus().setTextAlign(option.value).run()
69
+ }
70
+ disabled={!editor.can().setTextAlign(option.value)}
71
+ className={cn('justify-between', active && 'bg-accent')}
72
+ >
73
+ <span className="flex items-center gap-2">
74
+ <OptionIcon className="size-4" />
75
+ {option.label}
76
+ </span>
77
+ {active && <Check className="size-4" />}
78
+ </DropdownMenuItem>
79
+ );
80
+ })}
81
+ </DropdownMenuContent>
82
+ </DropdownMenu>
83
+ );
84
+ }
@@ -0,0 +1,54 @@
1
+ 'use client';
2
+
3
+ import { Redo, Undo } 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
+ interface UndoRedoToolbarButtonProps {
15
+ action: 'undo' | 'redo';
16
+ }
17
+
18
+ export function UndoRedoToolbarButton({ action }: UndoRedoToolbarButtonProps) {
19
+ const { editor } = useTiptapEditor();
20
+
21
+ if (!editor) return null;
22
+
23
+ const canExecute =
24
+ action === 'undo' ? editor.can().undo() : editor.can().redo();
25
+ const Icon = action === 'undo' ? Undo : Redo;
26
+
27
+ return (
28
+ <TooltipProvider>
29
+ <Tooltip delayDuration={700}>
30
+ <TooltipTrigger asChild>
31
+ <Button
32
+ variant="ghost"
33
+ size="sm"
34
+ onClick={() =>
35
+ action === 'undo'
36
+ ? editor.chain().focus().undo().run()
37
+ : editor.chain().focus().redo().run()
38
+ }
39
+ disabled={!canExecute}
40
+ className="size-6 px-0"
41
+ >
42
+ <Icon className="size-4" />
43
+ <span className="sr-only">
44
+ {action === 'undo' ? '撤销' : '重做'}
45
+ </span>
46
+ </Button>
47
+ </TooltipTrigger>
48
+ <TooltipContent>
49
+ <p>{action === 'undo' ? '撤销(⌘+Z)' : '重做(⌘+Shift+Z)'}</p>
50
+ </TooltipContent>
51
+ </Tooltip>
52
+ </TooltipProvider>
53
+ );
54
+ }