@@ -4,31 +4,13 @@ import React, { useState, useEffect, useMemo } from "react"
44import dynamic from "next/dynamic"
55import { type EditorConfig } from "ckeditor5"
66import type { Editor } from "@ckeditor/ckeditor5-core"
7-
8- import { Dialog , Typography } from "ol-components "
9- import { Course } from "./types"
7+ import { Dialog , Typography , SearchInput , LoadingSpinner } from "ol-components"
8+ import { useLearningResourcesSearch } from "api/hooks/learningResources "
9+ import type { Course } from "./types"
1010
1111import "ckeditor5/ckeditor5.css"
1212import "./styles.css"
1313
14- const mockCourses = [
15- {
16- id : "1" ,
17- title : "React for Beginners" ,
18- image :
19- "https://35904.cdn.cke-cs.com/A8zpYq0deQ8s5ZchTmUI/images/5025b810afce449ed73f6c18c929040073a27f14dd6ba674.webp" ,
20- description :
21- 'Free open source text" can refer to many programs, including text editors like Notepad++ and LibreOffice Writer, text-to-speech models like Mozilla TTS and eSpeak, and speech-to-text models such as Whisper and DeepSpeech. The best choice depends on the specific need, such as writing, editing code, or converting speech to tex' ,
22- } ,
23- {
24- id : "2" ,
25- title : "Next.js Advanced" ,
26- image :
27- "https://35904.cdn.cke-cs.com/A8zpYq0deQ8s5ZchTmUI/images/5025b810afce449ed73f6c18c929040073a27f14dd6ba674.webp" ,
28- description : "Master SSR and routing." ,
29- } ,
30- ]
31-
3214const CKEditor = dynamic (
3315 async ( ) => ( await import ( "@ckeditor/ckeditor5-react" ) ) . CKEditor ,
3416 { ssr : false } ,
@@ -47,13 +29,24 @@ export const CKEditorClient: React.FC<CKEditorClientProps> = ({
4729 uploadUrl,
4830 getCKEditorTokenFetchUrl,
4931} ) => {
32+ // --- Core editor and modal states
5033 // eslint-disable-next-line @typescript-eslint/no-explicit-any
5134 const [ EditorModules , setEditorModules ] = useState < any > ( null )
5235 const [ data , setData ] = useState ( value || "" )
5336 const [ open , setOpen ] = useState ( false )
5437
55- // store selected course info (for Dialog content)
56- const [ selectedCourse ] = useState < Course | null > ( null )
38+ const [ query , setQuery ] = useState ( "" )
39+ const [ debouncedQuery , setDebouncedQuery ] = useState ( "" )
40+
41+ useEffect ( ( ) => {
42+ const timeout = setTimeout ( ( ) => setDebouncedQuery ( query ) , 400 )
43+ return ( ) => clearTimeout ( timeout )
44+ } , [ query ] )
45+
46+ const { data : coursesData , isLoading } = useLearningResourcesSearch (
47+ { q : debouncedQuery } ,
48+ { keepPreviousData : true } ,
49+ )
5750
5851 useEffect ( ( ) => {
5952 const handler = ( e : CustomEvent ) => {
@@ -66,13 +59,12 @@ export const CKEditorClient: React.FC<CKEditorClientProps> = ({
6659 } , [ ] )
6760
6861 const handleCourseSelect = ( course : Course ) => {
69- if ( ! EditorModules ) {
70- console . warn ( "Editor instance not ready" )
71- setOpen ( false )
72- return
73- }
62+ if ( ! EditorModules ) return
7463 setOpen ( false )
75- EditorModules . execute ( "insertCourse" , course )
64+ EditorModules . execute ( "insertCourse" , {
65+ ...course ,
66+ image : course . image || "" ,
67+ } )
7668 EditorModules . editing . view . focus ( )
7769 }
7870
@@ -106,7 +98,6 @@ export const CKEditorClient: React.FC<CKEditorClientProps> = ({
10698 Underline,
10799 Strikethrough,
108100 Alignment,
109-
110101 CodeBlock,
111102 FontSize,
112103 FontColor,
@@ -161,11 +152,10 @@ export const CKEditorClient: React.FC<CKEditorClientProps> = ({
161152 ImageStyleEditing,
162153 ImageStyleUI,
163154 MediaEmbed,
164- Widget, // ✅ ensure this line exists
155+ Widget,
165156 MediaFloatPlugin,
166157 DefaultImageStylePlugin,
167158 InsertCoursePlugin,
168- // also expose the original modules object if you want:
169159 _CKEditorModules : CKEditorModules ,
170160 } )
171161 } ) ( )
@@ -185,23 +175,32 @@ export const CKEditorClient: React.FC<CKEditorClientProps> = ({
185175 EditorModules . List ,
186176 EditorModules . BlockQuote ,
187177 EditorModules . Autoformat ,
188- // 👇 Image-related plugins (all required for resizing + styling)
178+ EditorModules . Underline ,
179+ EditorModules . Strikethrough ,
180+ EditorModules . Alignment ,
181+ EditorModules . CodeBlock ,
182+ EditorModules . FontSize ,
183+ EditorModules . FontColor ,
184+ EditorModules . FontBackgroundColor ,
185+ EditorModules . Undo ,
186+ EditorModules . Table ,
187+ EditorModules . TableToolbar ,
189188 EditorModules . Image ,
190189 EditorModules . ImageToolbar ,
191190 EditorModules . ImageUpload ,
192191 EditorModules . EasyImage ,
192+ EditorModules . CloudServices ,
193193 EditorModules . ImageResize ,
194194 EditorModules . ImageResizeEditing ,
195195 EditorModules . ImageResizeHandles ,
196196 EditorModules . ImageStyle ,
197197 EditorModules . ImageStyleEditing ,
198198 EditorModules . ImageStyleUI ,
199- EditorModules . CloudServices ,
200199 EditorModules . MediaEmbed ,
201- EditorModules . Widget , // ✅ add Widget here
202- EditorModules . MediaFloatPlugin , // ✅ your plugin now safe
203- EditorModules . DefaultImageStylePlugin , // ✅ your plugin now safe
204- EditorModules . InsertCoursePlugin , // ✅ your plugin now safe
200+ EditorModules . Widget ,
201+ EditorModules . MediaFloatPlugin ,
202+ EditorModules . DefaultImageStylePlugin ,
203+ EditorModules . InsertCoursePlugin ,
205204 ] ,
206205 toolbar : {
207206 items : [
@@ -216,7 +215,7 @@ export const CKEditorClient: React.FC<CKEditorClientProps> = ({
216215 "numberedList" ,
217216 "strikethrough" ,
218217 "|" ,
219- "alignment" , // text alignment
218+ "alignment" ,
220219 "|" ,
221220 "blockQuote" ,
222221 "|" ,
@@ -235,7 +234,7 @@ export const CKEditorClient: React.FC<CKEditorClientProps> = ({
235234 "undo" ,
236235 "redo" ,
237236 "|" ,
238- "insertCourse" , // 👈 our custom button
237+ "insertCourse" ,
239238 "|" ,
240239 ] ,
241240 } ,
@@ -287,12 +286,12 @@ export const CKEditorClient: React.FC<CKEditorClientProps> = ({
287286 editor = { EditorModules . ClassicEditor }
288287 data = { data }
289288 config = { editorConfig }
290- onReady = { ( editor ) => {
289+ onReady = { ( editor ) =>
291290 setEditorModules ( ( prev : Editor ) => ( {
292291 ...prev ,
293292 _activeEditor : editor ,
294293 } ) )
295- } }
294+ }
296295 onChange = { ( _ , editor ) => {
297296 const html = editor . getData ( )
298297 setData ( html )
@@ -304,73 +303,90 @@ export const CKEditorClient: React.FC<CKEditorClientProps> = ({
304303 onClose = { ( ) => setOpen ( false ) }
305304 open = { open }
306305 title = "Select a Course"
307- actions = { null }
308306 >
309- { ! selectedCourse ? (
310- < >
311- < Typography sx = { { marginBottom : "16px" } } >
312- Choose a course to insert into the editor:
313- </ Typography >
307+ < div style = { { display : "flex" , flexDirection : "column" , gap : "16px" } } >
308+ < SearchInput
309+ value = { query }
310+ onChange = { ( e ) => setQuery ( e . target . value ) }
311+ onClear = { ( ) => setQuery ( "" ) }
312+ onSubmit = { ( e ) => setDebouncedQuery ( e . target . value ) } // triggers search
313+ placeholder = "Search for courses..."
314+ fullWidth
315+ />
316+
317+ { isLoading ? (
318+ < div
319+ style = { {
320+ display : "flex" ,
321+ justifyContent : "center" ,
322+ padding : "20px 0" ,
323+ } }
324+ >
325+ < LoadingSpinner color = "inherit" loading = { isLoading } size = { 16 } />
326+ </ div >
327+ ) : (
314328 < div
315329 style = { {
316330 display : "flex" ,
317331 flexDirection : "column" ,
318- gap : "16px " ,
332+ gap : "12px " ,
319333 maxHeight : "400px" ,
320334 overflowY : "auto" ,
321335 } }
322336 >
323- { mockCourses . map ( ( course ) => (
324- < div
325- key = { course . id }
326- role = "button"
327- tabIndex = { 0 }
328- onClick = { ( ) => handleCourseSelect ( course ) }
329- onKeyDown = { ( e ) => {
330- if ( e . key === "Enter" || e . key === " " ) {
331- e . preventDefault ( )
332- handleCourseSelect ( course )
333- }
334- } }
335- className = "course-list-container"
336- onFocus = { ( e ) =>
337- ( e . currentTarget . style . boxShadow = "0 0 0 2px #007aff33" )
338- }
339- onBlur = { ( e ) => ( e . currentTarget . style . boxShadow = "none" ) }
340- onMouseEnter = { ( e ) =>
341- ( e . currentTarget . style . background = "#fafafa" )
337+ { coursesData ?. results ?. length ? (
338+ coursesData . results . map ( ( resource ) => {
339+ const course : Course = {
340+ id : resource . id ,
341+ title : resource . title ,
342+ description : resource . description || "" ,
343+ image : resource . image ?. url || "" ,
342344 }
343- onMouseLeave = { ( e ) =>
344- ( e . currentTarget . style . background = "white" )
345- }
346- >
347- < img
348- src = { course . image }
349- alt = { course . title }
350- style = { {
351- width : "80px" ,
352- height : "60px" ,
353- objectFit : "cover" ,
354- borderRadius : "6px" ,
355- } }
356- />
357- < div style = { { flex : 1 } } >
358- < Typography variant = "subtitle1" > { course . title } </ Typography >
359- < Typography variant = "body2" sx = { { color : "#555" } } >
360- { course . description }
361- </ Typography >
362- </ div >
363- </ div >
364- ) ) }
345+
346+ return (
347+ < div
348+ key = { course . id }
349+ role = "button"
350+ tabIndex = { 0 }
351+ onClick = { ( ) => handleCourseSelect ( course ) }
352+ className = "course-list-container"
353+ onKeyDown = { ( e ) =>
354+ [ "Enter" , " " ] . includes ( e . key ) &&
355+ handleCourseSelect ( course )
356+ }
357+ >
358+ < img
359+ src = { course . image }
360+ alt = { course . title }
361+ style = { {
362+ width : "80px" ,
363+ height : "60px" ,
364+ objectFit : "cover" ,
365+ borderRadius : "6px" ,
366+ } }
367+ />
368+ < div style = { { flex : 1 , marginLeft : "12px" } } >
369+ < Typography variant = "subtitle2" >
370+ { course . title }
371+ </ Typography >
372+ { course . short_description && (
373+ < Typography
374+ variant = "body2"
375+ sx = { { color : "gray" , fontSize : "0.85rem" } }
376+ >
377+ { course . short_description . slice ( 0 , 80 ) } …
378+ </ Typography >
379+ ) }
380+ </ div >
381+ </ div >
382+ )
383+ } )
384+ ) : (
385+ < Typography > No courses found.</ Typography >
386+ ) }
365387 </ div >
366- </ >
367- ) : (
368- < >
369- < Typography sx = { { marginBottom : "16px" } } >
370- { selectedCourse . description }
371- </ Typography >
372- </ >
373- ) }
388+ ) }
389+ </ div >
374390 </ Dialog >
375391 </ >
376392 )
0 commit comments