@lark-apaas/coding-template-nestjs-react-fullstack 0.1.36-alpha.20260902024923 → 0.1.36-alpha.20260902085217
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/business-ui/tiptap-editor/README.md +324 -0
- package/template/client/src/components/business-ui/tiptap-editor/components/attachment-toolbar-button.tsx +74 -0
- package/template/client/src/components/business-ui/tiptap-editor/components/blockquote-toolbar-button.tsx +41 -0
- package/template/client/src/components/business-ui/tiptap-editor/components/code-block-toolbar-button.tsx +41 -0
- package/template/client/src/components/business-ui/tiptap-editor/components/color-highlight-toolbar-button.tsx +141 -0
- package/template/client/src/components/business-ui/tiptap-editor/components/heading-toolbar-button.tsx +99 -0
- package/template/client/src/components/business-ui/tiptap-editor/components/horizontal-rule-toolbar-button.tsx +39 -0
- package/template/client/src/components/business-ui/tiptap-editor/components/image-upload-toolbar-button.tsx +65 -0
- package/template/client/src/components/business-ui/tiptap-editor/components/link-edit-form.tsx +127 -0
- package/template/client/src/components/business-ui/tiptap-editor/components/link-hover-toolbar.tsx +380 -0
- package/template/client/src/components/business-ui/tiptap-editor/components/link-toolbar-button.tsx +96 -0
- package/template/client/src/components/business-ui/tiptap-editor/components/list-toolbar-button.tsx +75 -0
- package/template/client/src/components/business-ui/tiptap-editor/components/mark-toolbar-button.tsx +118 -0
- package/template/client/src/components/business-ui/tiptap-editor/components/text-align-toolbar-button.tsx +84 -0
- package/template/client/src/components/business-ui/tiptap-editor/components/undo-redo-toolbar-button.tsx +54 -0
- package/template/client/src/components/business-ui/tiptap-editor/extensions/attachment.tsx +539 -0
- package/template/client/src/components/business-ui/tiptap-editor/extensions/code-block-shiki.tsx +236 -0
- package/template/client/src/components/business-ui/tiptap-editor/extensions/complete-kit.ts +260 -0
- package/template/client/src/components/business-ui/tiptap-editor/extensions/image.tsx +456 -0
- package/template/client/src/components/business-ui/tiptap-editor/hooks/use-tiptap-editor.ts +47 -0
- package/template/client/src/components/business-ui/tiptap-editor/index.ts +38 -0
- package/template/client/src/components/business-ui/tiptap-editor/tiptap-editor-complete.tsx +112 -0
- package/template/client/src/components/business-ui/tiptap-editor/tiptap-editor.tsx +169 -0
- package/template/client/src/components/ui/streamdown.tsx +186 -0
- package/template/client/src/index.css +1 -0
- package/template/client/src/lib/shiki.ts +92 -0
- package/template/nest-cli.json +9 -10
- package/template/package-lock.json +18307 -9660
- package/template/package.json +68 -5
- package/template/client/src/components/ui/markdown.tsx +0 -43
package/template/client/src/components/business-ui/tiptap-editor/components/link-toolbar-button.tsx
ADDED
|
@@ -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
|
+
}
|
package/template/client/src/components/business-ui/tiptap-editor/components/list-toolbar-button.tsx
ADDED
|
@@ -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
|
+
}
|
package/template/client/src/components/business-ui/tiptap-editor/components/mark-toolbar-button.tsx
ADDED
|
@@ -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
|
+
}
|