@lark-apaas/coding-template-nestjs-react-fullstack 0.1.36-alpha.20260902024923 → 0.1.36-alpha.20260902103327

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 (31) 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 +18307 -9660
  30. package/template/package.json +68 -5
  31. package/template/client/src/components/ui/markdown.tsx +0 -43
@@ -0,0 +1,169 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import {
5
+ EditorContent,
6
+ EditorContext,
7
+ useCurrentEditor,
8
+ useEditor,
9
+ type EditorContentProps,
10
+ type Extensions,
11
+ } from '@tiptap/react';
12
+ import { Slot } from 'radix-ui';
13
+
14
+ import { LinkHoverToolbar } from '@/components/business-ui/tiptap-editor/components/link-hover-toolbar';
15
+ import { CompleteKit } from '@/components/business-ui/tiptap-editor/extensions/complete-kit';
16
+ import { cn } from '@/lib/utils';
17
+ import { Separator } from '@/components/ui/separator';
18
+
19
+ export interface TiptapEditorProps extends React.ComponentProps<'div'> {
20
+ /** 受控 value (HTML)。 */
21
+ value?: string;
22
+ /** 默认值 (HTML)。 */
23
+ defaultValue?: string;
24
+ /** 当编辑器更新时,触发并传递最新的 HTML。 */
25
+ onValueChange?: (value: string) => void;
26
+ children?: React.ReactNode;
27
+ className?: string;
28
+ /**
29
+ * 自定义 Tiptap 扩展。
30
+ * @default [CompleteKit]
31
+ */
32
+ extensions?: Extensions;
33
+ /** 是否为只读模式。 */
34
+ readOnly?: boolean;
35
+ asChild?: boolean;
36
+ }
37
+
38
+ const defaultExtensions = [CompleteKit];
39
+
40
+ export function TiptapEditor({
41
+ value,
42
+ defaultValue,
43
+ onValueChange,
44
+ children,
45
+ className,
46
+ extensions = defaultExtensions,
47
+ readOnly = false,
48
+ asChild = false,
49
+ autoFocus = false,
50
+ 'aria-disabled': ariaDisabled,
51
+ ...props
52
+ }: TiptapEditorProps) {
53
+ const Comp = asChild ? Slot.Root : 'div';
54
+
55
+ const initialContent = value !== undefined ? value : defaultValue;
56
+ const editable = !readOnly && !ariaDisabled;
57
+
58
+ const editor = useEditor({
59
+ autofocus: autoFocus,
60
+ editable,
61
+ editorProps: {
62
+ attributes: {
63
+ class: cn('prose max-w-none flex-1 px-3 py-2 dark:prose-invert'),
64
+ },
65
+ },
66
+ immediatelyRender: false,
67
+ content: initialContent,
68
+ onUpdate: ({ editor }) => {
69
+ onValueChange?.(editor.getHTML());
70
+ },
71
+ extensions,
72
+ });
73
+
74
+ const valueRef = React.useRef(value);
75
+ React.useEffect(() => {
76
+ if (!editor || value === undefined) return;
77
+ valueRef.current = value;
78
+
79
+ // Tiptap 内部会使用 flushSync,因此我们将内容更新延迟到微任务队列
80
+ queueMicrotask(() => {
81
+ const current = editor.getHTML();
82
+ if (valueRef.current !== current) {
83
+ editor.commands.setContent(value);
84
+ }
85
+ });
86
+ }, [value, editor]);
87
+
88
+ React.useEffect(() => {
89
+ if (!editor) return;
90
+ editor.setEditable(editable);
91
+ }, [editor, editable]);
92
+
93
+ const contextValue = React.useMemo(() => ({ editor }), [editor]);
94
+
95
+ return (
96
+ <EditorContext value={contextValue}>
97
+ <Comp
98
+ aria-disabled={ariaDisabled}
99
+ className={cn(
100
+ "relative flex w-full flex-col rounded-md border ring-offset-background focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/20 focus-within:outline-hidden hover:border-ring aria-disabled:cursor-not-allowed aria-disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 **:data-[type='taskList']:list-none **:data-[type='taskList']:p-0 [&_[data-type='taskList']>li]:flex [&_[data-type='taskList']>li]:items-start [&_[data-type='taskList']>li]:gap-2 [&_[data-type='taskList']>li>div]:min-w-0 [&_[data-type='taskList']>li>div]:flex-1 [&_[data-type='taskList']>li>div>p]:m-0 [&_[data-type='taskList']>li>label]:mt-0.5 [&_[data-type='taskList']>li>label]:shrink-0 **:[a]:cursor-pointer",
101
+ className,
102
+ )}
103
+ data-slot="tiptap-editor"
104
+ {...props}
105
+ >
106
+ {children}
107
+ <LinkHoverToolbar />
108
+ </Comp>
109
+ </EditorContext>
110
+ );
111
+ }
112
+
113
+ export function TiptapEditorContent({
114
+ className,
115
+ ...props
116
+ }: Omit<EditorContentProps, 'editor'>) {
117
+ const { editor } = useCurrentEditor();
118
+
119
+ return (
120
+ <EditorContent
121
+ editor={editor}
122
+ className={cn(
123
+ 'flex flex-1 flex-col overflow-y-auto outline-hidden',
124
+ className,
125
+ )}
126
+ data-slot="tiptap-editor-content"
127
+ {...props}
128
+ />
129
+ );
130
+ }
131
+
132
+ export function TiptapEditorToolbar({
133
+ className,
134
+ children,
135
+ asChild,
136
+ ...props
137
+ }: React.ComponentProps<'div'> & { asChild?: boolean }) {
138
+ const Comp = asChild ? Slot.Root : 'div';
139
+
140
+ return (
141
+ <Comp
142
+ className={cn(
143
+ 'flex flex-wrap items-center gap-x-1 gap-y-2 p-1.5',
144
+ className,
145
+ )}
146
+ data-slot="tiptap-editor-toolbar"
147
+ {...props}
148
+ >
149
+ {children}
150
+ </Comp>
151
+ );
152
+ }
153
+
154
+ export function TiptapEditorToolbarSeparator({
155
+ className,
156
+ ...props
157
+ }: React.ComponentProps<typeof Separator>) {
158
+ return (
159
+ <Separator
160
+ orientation="vertical"
161
+ className={cn(
162
+ 'mx-0.5 h-4 w-px bg-border data-[orientation=vertical]:h-4',
163
+ className,
164
+ )}
165
+ data-slot="tiptap-editor-toolbar-separator"
166
+ {...props}
167
+ />
168
+ );
169
+ }
@@ -0,0 +1,186 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { Streamdown as StreamdownPrimitive } from 'streamdown';
5
+
6
+ import { cn } from '@/lib/utils';
7
+ import { omit } from 'es-toolkit';
8
+
9
+ const defaultComponents: NonNullable<
10
+ React.ComponentProps<typeof StreamdownPrimitive>['components']
11
+ > = {
12
+ ol: ({ className, ...props }) => (
13
+ <ol
14
+ className={cn(className)}
15
+ data-streamdown="ordered-list"
16
+ {...omit(props, ['node'])}
17
+ />
18
+ ),
19
+ li: ({ className, ...props }) => (
20
+ <li
21
+ className={cn(className)}
22
+ data-streamdown="list-item"
23
+ {...omit(props, ['node'])}
24
+ />
25
+ ),
26
+ ul: ({ className, ...props }) => (
27
+ <ul
28
+ className={cn(className)}
29
+ data-streamdown="unordered-list"
30
+ {...omit(props, ['node'])}
31
+ />
32
+ ),
33
+ hr: ({ className, ...props }) => (
34
+ <hr
35
+ className={cn(className)}
36
+ data-streamdown="horizontal-rule"
37
+ {...omit(props, ['node'])}
38
+ />
39
+ ),
40
+ strong: ({ className, ...props }) => (
41
+ <strong
42
+ className={cn(className)}
43
+ data-streamdown="strong"
44
+ {...omit(props, ['node'])}
45
+ />
46
+ ),
47
+ a: ({ className, href, ...props }) => {
48
+ const isIncomplete = href === 'streamdown:incomplete-link';
49
+
50
+ return (
51
+ <a
52
+ className={cn(className)}
53
+ data-incomplete={isIncomplete}
54
+ data-streamdown="link"
55
+ href={href}
56
+ rel={props.rel ?? 'noreferrer'}
57
+ target={props.target ?? '_blank'}
58
+ {...omit(props, ['node'])}
59
+ />
60
+ );
61
+ },
62
+ h1: ({ className, ...props }) => (
63
+ <h1
64
+ className={cn(className)}
65
+ data-streamdown="heading-1"
66
+ {...omit(props, ['node'])}
67
+ />
68
+ ),
69
+ h2: ({ className, ...props }) => (
70
+ <h2
71
+ className={cn(className)}
72
+ data-streamdown="heading-2"
73
+ {...omit(props, ['node'])}
74
+ />
75
+ ),
76
+ h3: ({ className, ...props }) => (
77
+ <h3
78
+ className={cn(className)}
79
+ data-streamdown="heading-3"
80
+ {...omit(props, ['node'])}
81
+ />
82
+ ),
83
+ h4: ({ className, ...props }) => (
84
+ <h4
85
+ className={cn(className)}
86
+ data-streamdown="heading-4"
87
+ {...omit(props, ['node'])}
88
+ />
89
+ ),
90
+ h5: ({ className, ...props }) => (
91
+ <h5
92
+ className={cn(className)}
93
+ data-streamdown="heading-5"
94
+ {...omit(props, ['node'])}
95
+ />
96
+ ),
97
+ h6: ({ className, ...props }) => (
98
+ <h6
99
+ className={cn(className)}
100
+ data-streamdown="heading-6"
101
+ {...omit(props, ['node'])}
102
+ />
103
+ ),
104
+ table: ({ className, ...props }) => (
105
+ <table
106
+ className={cn(className)}
107
+ data-streamdown="table-wrapper"
108
+ {...omit(props, ['node'])}
109
+ />
110
+ ),
111
+ thead: ({ className, ...props }) => (
112
+ <thead
113
+ className={cn(className)}
114
+ data-streamdown="table-header"
115
+ {...omit(props, ['node'])}
116
+ />
117
+ ),
118
+ tbody: ({ className, ...props }) => (
119
+ <tbody
120
+ className={cn(className)}
121
+ data-streamdown="table-body"
122
+ {...omit(props, ['node'])}
123
+ />
124
+ ),
125
+ tr: ({ className, ...props }) => (
126
+ <tr
127
+ className={cn(className)}
128
+ data-streamdown="table-row"
129
+ {...omit(props, ['node'])}
130
+ />
131
+ ),
132
+ th: ({ className, ...props }) => (
133
+ <th
134
+ className={cn(className)}
135
+ data-streamdown="table-header-cell"
136
+ {...omit(props, ['node'])}
137
+ />
138
+ ),
139
+ td: ({ className, ...props }) => (
140
+ <td
141
+ className={cn(className)}
142
+ data-streamdown="table-cell"
143
+ {...omit(props, ['node'])}
144
+ />
145
+ ),
146
+ blockquote: ({ className, ...props }) => (
147
+ <blockquote
148
+ className={cn(className)}
149
+ data-streamdown="blockquote"
150
+ {...omit(props, ['node'])}
151
+ />
152
+ ),
153
+ sup: ({ className, ...props }) => (
154
+ <sup className={cn(className)} {...omit(props, ['node'])} />
155
+ ),
156
+ sub: ({ className, ...props }) => (
157
+ <sub className={cn(className)} {...omit(props, ['node'])} />
158
+ ),
159
+ p: ({ className, ...props }) => (
160
+ <p className={cn(className)} {...omit(props, ['node'])} />
161
+ ),
162
+ section: ({ className, ...props }) => (
163
+ <section className={cn(className)} {...omit(props, ['node'])} />
164
+ ),
165
+ img: ({ className, alt, ...props }) => (
166
+ <img className={cn(className)} alt={alt ?? ''} {...omit(props, ['node'])} />
167
+ ),
168
+ pre: ({ children }) => <div className="not-prose">{children}</div>,
169
+ };
170
+
171
+ export function Streamdown({
172
+ className,
173
+ components,
174
+ ...props
175
+ }: React.ComponentProps<typeof StreamdownPrimitive>) {
176
+ return (
177
+ <StreamdownPrimitive
178
+ className={cn(
179
+ 'prose max-w-none dark:prose-invert',
180
+ className,
181
+ )}
182
+ components={{ ...defaultComponents, ...components }}
183
+ {...props}
184
+ />
185
+ );
186
+ }
@@ -3,4 +3,5 @@
3
3
  @import "./tailwind-theme.css";
4
4
  @import "./typography.css";
5
5
 
6
+ @source "../../node_modules/streamdown/dist/*.js";
6
7
  @config "../../tailwind.config.ts";
@@ -0,0 +1,92 @@
1
+ import {
2
+ createBundledHighlighter,
3
+ createSingletonShorthands,
4
+ } from 'shiki/core';
5
+ import { createJavaScriptRegexEngine } from 'shiki/engine/javascript';
6
+ import type {
7
+ DynamicImportLanguageRegistration,
8
+ DynamicImportThemeRegistration,
9
+ HighlighterGeneric,
10
+ } from 'shiki/types';
11
+
12
+ type BundledLanguage =
13
+ | 'typescript'
14
+ | 'ts'
15
+ | 'cts'
16
+ | 'mts'
17
+ | 'javascript'
18
+ | 'js'
19
+ | 'cjs'
20
+ | 'mjs'
21
+ | 'jsx'
22
+ | 'tsx'
23
+ | 'html'
24
+ | 'css'
25
+ | 'json'
26
+ | 'jsonc'
27
+ | 'json5'
28
+ | 'markdown'
29
+ | 'md';
30
+ type BundledTheme = 'github-light' | 'github-dark';
31
+ type Highlighter = HighlighterGeneric<BundledLanguage, BundledTheme>;
32
+
33
+ const bundledLanguages = {
34
+ typescript: () => import('@shikijs/langs/typescript'),
35
+ ts: () => import('@shikijs/langs/typescript'),
36
+ cts: () => import('@shikijs/langs/typescript'),
37
+ mts: () => import('@shikijs/langs/typescript'),
38
+ javascript: () => import('@shikijs/langs/javascript'),
39
+ js: () => import('@shikijs/langs/javascript'),
40
+ cjs: () => import('@shikijs/langs/javascript'),
41
+ mjs: () => import('@shikijs/langs/javascript'),
42
+ jsx: () => import('@shikijs/langs/jsx'),
43
+ tsx: () => import('@shikijs/langs/tsx'),
44
+ html: () => import('@shikijs/langs/html'),
45
+ css: () => import('@shikijs/langs/css'),
46
+ json: () => import('@shikijs/langs/json'),
47
+ jsonc: () => import('@shikijs/langs/jsonc'),
48
+ json5: () => import('@shikijs/langs/json5'),
49
+ markdown: () => import('@shikijs/langs/markdown'),
50
+ md: () => import('@shikijs/langs/markdown'),
51
+ } as Record<BundledLanguage, DynamicImportLanguageRegistration>;
52
+
53
+ const bundledThemes = {
54
+ 'github-light': () => import('@shikijs/themes/github-light'),
55
+ 'github-dark': () => import('@shikijs/themes/github-dark'),
56
+ } as Record<BundledTheme, DynamicImportThemeRegistration>;
57
+
58
+ const createHighlighter = /* @__PURE__ */ createBundledHighlighter<
59
+ BundledLanguage,
60
+ BundledTheme
61
+ >({
62
+ langs: bundledLanguages,
63
+ themes: bundledThemes,
64
+ engine: () => createJavaScriptRegexEngine(),
65
+ });
66
+
67
+ const {
68
+ codeToHtml,
69
+ codeToHast,
70
+ codeToTokensBase,
71
+ codeToTokens,
72
+ codeToTokensWithThemes,
73
+ getSingletonHighlighter,
74
+ getLastGrammarState,
75
+ } = /* @__PURE__ */ createSingletonShorthands<BundledLanguage, BundledTheme>(
76
+ createHighlighter,
77
+ );
78
+
79
+ export {
80
+ bundledLanguages,
81
+ bundledThemes,
82
+ codeToHast,
83
+ codeToHtml,
84
+ codeToTokens,
85
+ codeToTokensBase,
86
+ codeToTokensWithThemes,
87
+ createHighlighter,
88
+ getLastGrammarState,
89
+ getSingletonHighlighter,
90
+ };
91
+
92
+ export type { BundledLanguage, BundledTheme, Highlighter };
@@ -5,21 +5,20 @@
5
5
  "compilerOptions": {
6
6
  "deleteOutDir": false,
7
7
  "tsConfigPath": "tsconfig.node.json",
8
+ "typeCheck": true,
9
+ "builder": {
10
+ "type": "swc",
11
+ "options": {
12
+ "filenames": ["server", "shared"],
13
+ "stripLeadingPaths": false
14
+ }
15
+ },
8
16
  "assets": [
9
17
  {
10
18
  "include": "capabilities/**/*.json",
11
19
  "outDir": "dist/server",
12
20
  "watchAssets": true
13
21
  }
14
- ],
15
- "plugins": [
16
- {
17
- "name": "@nestjs/swagger",
18
- "options": {
19
- "introspectComments": true,
20
- "classValidatorShim": true
21
- }
22
- }
23
22
  ]
24
23
  }
25
- }
24
+ }