Skip to content

Commit 642ebe0

Browse files
authored
Merge branch 'main' into vh/feat/aih/special-treatment-for-shellscript-code-blocks
2 parents cfcb36a + b8dcdde commit 642ebe0

32 files changed

+2574
-9
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import React from 'react'
2+
3+
import { useSelection } from './selection-context'
4+
5+
export function DynamicTitle() {
6+
const { excludedIds } = useSelection()
7+
return (
8+
<span className="mb-3 flex px-5 pt-5 text-lg font-bold">
9+
{excludedIds.length > 0
10+
? 'In the list'
11+
: 'Start by adding resources below.'}
12+
</span>
13+
)
14+
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import * as React from 'react'
2+
import { useRouter } from 'next/navigation'
3+
import type { List, ListSchema } from '@/lib/lists'
4+
import type { UseFormReturn } from 'react-hook-form'
5+
import { z } from 'zod'
6+
7+
import { VideoResource } from '@coursebuilder/core/schemas/video-resource'
8+
import {
9+
Button,
10+
FormControl,
11+
FormDescription,
12+
FormField,
13+
FormItem,
14+
FormLabel,
15+
FormMessage,
16+
Input,
17+
Select,
18+
SelectContent,
19+
SelectItem,
20+
SelectTrigger,
21+
SelectValue,
22+
Textarea,
23+
} from '@coursebuilder/ui'
24+
import { useSocket } from '@coursebuilder/ui/hooks/use-socket'
25+
import { MetadataFieldState } from '@coursebuilder/ui/resources-crud/metadata-fields/metadata-field-state'
26+
import { MetadataFieldVisibility } from '@coursebuilder/ui/resources-crud/metadata-fields/metadata-field-visibility'
27+
import AdvancedTagSelector from '@coursebuilder/ui/resources-crud/tag-selector'
28+
29+
export const ListMetadataFormFields: React.FC<{
30+
form: UseFormReturn<z.infer<typeof ListSchema>>
31+
list: List
32+
}> = ({ form, list }) => {
33+
const router = useRouter()
34+
35+
return (
36+
<>
37+
<FormField
38+
control={form.control}
39+
name="id"
40+
render={({ field }) => <Input type="hidden" {...field} />}
41+
/>
42+
43+
<FormField
44+
control={form.control}
45+
name="fields.title"
46+
render={({ field }) => (
47+
<FormItem className="px-5">
48+
<FormLabel className="text-lg font-bold">Title</FormLabel>
49+
<FormDescription>
50+
A title should summarize the post and explain what it is about
51+
clearly.
52+
</FormDescription>
53+
<Input {...field} />
54+
<FormMessage />
55+
</FormItem>
56+
)}
57+
/>
58+
<FormField
59+
control={form.control}
60+
name="fields.slug"
61+
render={({ field }) => (
62+
<FormItem className="px-5">
63+
<FormLabel className="text-lg font-bold">Slug</FormLabel>
64+
<FormDescription>Short with keywords is best.</FormDescription>
65+
<Input {...field} />
66+
<FormMessage />
67+
</FormItem>
68+
)}
69+
/>
70+
<FormField
71+
control={form.control}
72+
name="fields.type"
73+
render={({ field }) => {
74+
return (
75+
<FormItem className="px-5">
76+
<FormLabel className="text-lg font-bold">Type</FormLabel>
77+
<Select
78+
onValueChange={field.onChange}
79+
defaultValue={field.value || 'nextUp'}
80+
>
81+
<FormControl>
82+
<SelectTrigger className="w-full">
83+
<SelectValue placeholder="Select product type..." />
84+
</SelectTrigger>
85+
</FormControl>
86+
<SelectContent>
87+
<SelectItem value="nextUp">Next Up</SelectItem>
88+
</SelectContent>
89+
</Select>
90+
<FormMessage />
91+
</FormItem>
92+
)
93+
}}
94+
/>
95+
<FormField
96+
control={form.control}
97+
name="fields.description"
98+
render={({ field }) => (
99+
<FormItem className="px-5">
100+
<FormLabel className="text-lg font-bold">
101+
SEO Description ({`${field.value?.length ?? '0'} / 160`})
102+
</FormLabel>
103+
<FormDescription>
104+
A short snippet that summarizes the post in under 160 characters.
105+
Keywords should be included to support SEO.
106+
</FormDescription>
107+
<Textarea {...field} value={field.value ?? ''} />
108+
{field.value && field.value.length > 160 && (
109+
<FormMessage>
110+
Your description is longer than 160 characters
111+
</FormMessage>
112+
)}
113+
<FormMessage />
114+
</FormItem>
115+
)}
116+
/>
117+
<MetadataFieldVisibility form={form} />
118+
<MetadataFieldState form={form} />
119+
<FormField
120+
control={form.control}
121+
name="fields.github"
122+
render={({ field }) => (
123+
<FormItem className="px-5">
124+
<FormLabel className="text-lg font-bold">GitHub</FormLabel>
125+
<FormDescription>
126+
Direct link to the GitHub file associated with the post.
127+
</FormDescription>
128+
<Input {...field} value={field.value ?? ''} />
129+
<FormMessage />
130+
</FormItem>
131+
)}
132+
/>
133+
<FormField
134+
control={form.control}
135+
name="fields.gitpod"
136+
render={({ field }) => (
137+
<FormItem className="px-5">
138+
<FormLabel className="text-lg font-bold">Gitpod</FormLabel>
139+
<FormDescription>
140+
Gitpod link to start a new workspace with the post.
141+
</FormDescription>
142+
<Input {...field} value={field.value ?? ''} />
143+
<FormMessage />
144+
</FormItem>
145+
)}
146+
/>
147+
</>
148+
)
149+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
'use client'
2+
3+
import * as React from 'react'
4+
import { PostMetadataFormFields } from '@/app/(content)/posts/_components/edit-post-form-metadata'
5+
import { ImageResourceUploader } from '@/components/image-uploader/image-resource-uploader'
6+
import { env } from '@/env.mjs'
7+
import { useIsMobile } from '@/hooks/use-is-mobile'
8+
import { sendResourceChatMessage } from '@/lib/ai-chat-query'
9+
import { List, ListSchema } from '@/lib/lists'
10+
import { updateList } from '@/lib/lists-query'
11+
import { Post, PostSchema } from '@/lib/posts'
12+
import { autoUpdatePost, updatePost } from '@/lib/posts-query'
13+
import type { Tag } from '@/lib/tags'
14+
import { zodResolver } from '@hookform/resolvers/zod'
15+
import { ImagePlusIcon } from 'lucide-react'
16+
import { useSession } from 'next-auth/react'
17+
import { useTheme } from 'next-themes'
18+
import { useForm, type UseFormReturn } from 'react-hook-form'
19+
import { z } from 'zod'
20+
21+
import { ContentResourceSchema } from '@coursebuilder/core/schemas'
22+
import { VideoResource } from '@coursebuilder/core/schemas/video-resource'
23+
import { EditResourcesFormDesktop } from '@coursebuilder/ui/resources-crud/edit-resources-form-desktop'
24+
25+
import { ListMetadataFormFields } from './edit-list-form-metadata'
26+
import ListResoucesEdit from './list-resources-edit'
27+
28+
// import { MobileEditPostForm } from './edit-post-form-mobile'
29+
30+
const NewPostFormSchema = z.object({
31+
title: z.string().min(2).max(90),
32+
body: z.string().nullish(),
33+
visibility: z.enum(['public', 'unlisted', 'private']),
34+
description: z.string().nullish(),
35+
github: z.string().nullish(),
36+
gitpod: z.string().nullish(),
37+
})
38+
39+
export type EditListFormProps = {
40+
list: List
41+
form: UseFormReturn<z.infer<typeof ListSchema>>
42+
children?: React.ReactNode
43+
availableWorkflows?: { value: string; label: string; default?: boolean }[]
44+
theme?: string
45+
}
46+
47+
export function EditListForm({ list }: Omit<EditListFormProps, 'form'>) {
48+
const { forcedTheme: theme } = useTheme()
49+
const session = useSession()
50+
const form = useForm<z.infer<typeof ListSchema>>({
51+
resolver: zodResolver(NewPostFormSchema),
52+
defaultValues: {
53+
id: list.id,
54+
fields: {
55+
title: list.fields?.title,
56+
body: list.fields?.body,
57+
slug: list.fields?.slug,
58+
type: list.fields?.type,
59+
visibility: list.fields?.visibility || 'public',
60+
state: list.fields?.state || 'draft',
61+
description: list.fields?.description ?? '',
62+
github: list.fields?.github ?? '',
63+
gitpod: list.fields?.gitpod ?? '',
64+
},
65+
},
66+
})
67+
const isMobile = useIsMobile()
68+
69+
return (
70+
<EditResourcesFormDesktop
71+
resource={list}
72+
resourceSchema={ListSchema}
73+
getResourcePath={(slug) => `/lists`}
74+
updateResource={updateList}
75+
// autoUpdateResource={autoUpdatePost}
76+
form={form}
77+
bodyPanelSlot={<ListResoucesEdit list={list} />}
78+
availableWorkflows={[
79+
{
80+
value: 'prompt_list1',
81+
label: 'List Chat',
82+
default: true,
83+
},
84+
]}
85+
sendResourceChatMessage={sendResourceChatMessage}
86+
hostUrl={env.NEXT_PUBLIC_PARTY_KIT_URL}
87+
user={session?.data?.user}
88+
theme={theme}
89+
onResourceBodyChange={() => {}}
90+
tools={[
91+
{ id: 'assistant' },
92+
{
93+
id: 'media',
94+
icon: () => (
95+
<ImagePlusIcon strokeWidth={1.5} size={24} width={18} height={18} />
96+
),
97+
toolComponent: (
98+
<ImageResourceUploader
99+
key={'image-uploader'}
100+
belongsToResourceId={list.id}
101+
uploadDirectory={`posts`}
102+
/>
103+
),
104+
},
105+
]}
106+
>
107+
<React.Suspense fallback={<div>loading</div>}>
108+
<ListMetadataFormFields form={form} list={list} />
109+
</React.Suspense>
110+
</EditResourcesFormDesktop>
111+
)
112+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
'use client'
2+
3+
import type { TypesenseResource } from '@/lib/typesense'
4+
5+
import { Checkbox, Label } from '@coursebuilder/ui'
6+
import { cn } from '@coursebuilder/ui/utils/cn'
7+
8+
import type { TreeAction } from './lesson-list/data/tree'
9+
import { useSelection } from './selection-context'
10+
11+
export default function Hit({
12+
hit,
13+
}: {
14+
hit: TypesenseResource
15+
listId: string
16+
updateTreeState: React.ActionDispatch<[action: TreeAction]>
17+
}) {
18+
const { toggleSelection, isSelected } = useSelection()
19+
20+
return (
21+
<li
22+
className={cn('hover:bg-muted/50 group flex items-center gap-2 px-5', {
23+
'bg-muted': isSelected(hit.id),
24+
})}
25+
>
26+
<Checkbox
27+
onCheckedChange={() => toggleSelection(hit)}
28+
id={hit.id}
29+
checked={isSelected(hit.id)}
30+
/>
31+
<Label
32+
htmlFor={hit.id}
33+
className="group flex w-full flex-row items-baseline justify-between gap-2 py-3 sm:py-3"
34+
>
35+
<div className="flex items-center gap-2">
36+
<span className="pr-5 font-medium sm:truncate">{hit.title}</span>
37+
</div>
38+
<div className="text-muted-foreground fon-normal flex flex-shrink-0 items-center gap-3 pl-7 text-xs capitalize opacity-60 sm:pl-0">
39+
<span>{hit.type}</span>
40+
</div>
41+
</Label>
42+
</li>
43+
)
44+
}

0 commit comments

Comments
 (0)