PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.3.7
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.3.7
4.4.8 4.4.7 4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 4.4.0 4.3.9.1 4.3.9 4.3.8 4.3.7 4.1.6.9 4.1.6.9.1 4.1.6.9.2 4.1.6.9.3 4.1.6.9.4 4.1.7 4.1.7.1 4.1.7.2 4.1.7.3 4.1.7.3.1 4.1.7.3.2 4.2.0 All 139 releases
learnpress / assets / src / js / frontend / course-builder.js

course-builder.js in LearnPress – WordPress LMS Plugin for Create and Sell Online Courses 4.3.7, at assets/src/js/frontend/course-builder.js

339 lines 9.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Course builder JS handler.
3 *
4 * @since 4.3.6
5 * @version 1.0.0
6 */
7 import * as lpUtils from 'lpAssetsJsPath/utils.js';
8 import { BuilderTabCourse } from './course-builder/builder-course/builder-tab-course.js';
9 import { BuilderEditCourse } from './course-builder/builder-course/builder-edit-course.js';
10 import { CreateCourseViaAI } from '../admin/courses/generate-with-ai.js';
11 import { GenerateWithOpenai } from '../admin/generate-with-openai.js';
12 import { BuilderTabLesson } from './course-builder/builder-lesson/builder-tab-lesson.js';
13 import { BuilderEditLesson } from './course-builder/builder-lesson/builder-edit-lesson.js';
14 import { BuilderTabQuiz } from './course-builder/builder-quiz/builder-tab-quiz.js';
15 import { BuilderStandaloneQuiz } from './course-builder/builder-quiz/builder-standalone-quiz.js';
16 import { BuilderTabQuestion } from './course-builder/builder-question/builder-tab-question.js';
17 import { BuilderEditQuestion } from './course-builder/builder-question/builder-edit-question.js';
18 import { BuilderPopup } from './course-builder/builder-popup.js';
19 import { BuilderMaterial } from './course-builder/builder-lesson/builder-material.js';
20 import { BuilderDashboard } from './course-builder/builder-dashboard.js';
21 import { BuilderSettings } from './course-builder/builder-settings.js';
22 import { getFormState } from './course-builder/builder-form-state.js';
23 import { EditCurriculumAi } from '../admin/edit-course/edit-curriculum/edit-curriculum-ai.js';
24 import { initElsTomSelect } from 'lpAssetsJsPath/admin/init-tom-select.js';
25 import { Utils } from 'lpAssetsJsPath/admin/utils-admin.js';
26 import * as lpToastify from 'lpAssetsJsPath/lpToastify';
27
28 // Initialize all builder components
29 const initBuilderComponents = () => {
30 try {
31 new BuilderTabCourse();
32 new BuilderEditCourse();
33
34 const hasCreateCourseAiButton = document.querySelector(
35 '.lp-btn-generate-course-with-ai, .lp-btn-warning-enable-ai'
36 );
37 if ( hasCreateCourseAiButton ) {
38 new CreateCourseViaAI( {
39 autoInsertButton: false,
40 isCourseBuilder: true,
41 redirectDelayMs: 1200,
42 } );
43 }
44
45 const hasEditCourseAiButton = document.querySelector(
46 '.cb-course-edit-ai-btn.lp-btn-generate-with-ai'
47 );
48 if ( hasEditCourseAiButton ) {
49 new GenerateWithOpenai( {
50 autoInsertButtons: false,
51 isCourseBuilder: true,
52 } );
53 }
54
55 new BuilderTabLesson();
56 new BuilderEditLesson();
57 new BuilderTabQuiz();
58 new BuilderStandaloneQuiz();
59 new BuilderTabQuestion();
60 new BuilderEditQuestion();
61 new BuilderPopup();
62 new BuilderDashboard();
63 new BuilderSettings();
64 new EditCurriculumAi( { isCourseBuilder: true } );
65
66 // Initialize form state management for ClassPress-style UX
67 getFormState();
68
69 // Initialize sidebar toggle
70 initSidebarToggle();
71 initHeaderMoreActions();
72 } catch ( e ) {
73 console.error( 'Error initializing builder components:', e );
74 }
75 };
76
77 /**
78 * Initialize sidebar collapse/expand toggle
79 * Persists state in localStorage
80 */
81 const initSidebarToggle = () => {
82 const sidebar = document.getElementById( 'lp-course-builder-sidebar' );
83 const toggleBtn = document.querySelector( '.lp-cb-sidebar__toggle' );
84 const wrapper = document.getElementById( 'lp-course-builder' );
85 const storageKey = 'lp_cb_sidebar_collapsed';
86
87 if ( ! sidebar || ! toggleBtn ) {
88 return;
89 }
90
91 // Restore saved state
92 const isCollapsed = localStorage.getItem( storageKey ) === 'true';
93 if ( isCollapsed ) {
94 sidebar.classList.add( 'is-collapsed' );
95 if ( wrapper ) {
96 wrapper.classList.add( 'has-collapsed-sidebar' );
97 }
98 }
99
100 // Handle toggle click
101 toggleBtn.addEventListener( 'click', () => {
102 const willCollapse = ! sidebar.classList.contains( 'is-collapsed' );
103
104 sidebar.classList.toggle( 'is-collapsed' );
105 if ( wrapper ) {
106 wrapper.classList.toggle( 'has-collapsed-sidebar' );
107 }
108
109 // Save state
110 localStorage.setItem( storageKey, willCollapse ? 'true' : 'false' );
111 } );
112 };
113
114 /**
115 * Initialize "More actions" menu in edit header (duplicate/trash).
116 * Uses explicit open state to avoid focus-only behavior issues across browsers.
117 */
118 const initHeaderMoreActions = () => {
119 const wrapSelector = '.cb-header-action-expanded';
120 const toggleSelector = '.course-action-expanded';
121 const openClass = 'is-open';
122
123 const closeAll = ( exclude = null ) => {
124 document.querySelectorAll( `${ wrapSelector }.${ openClass }` ).forEach( ( wrap ) => {
125 if ( exclude && wrap === exclude ) {
126 return;
127 }
128
129 wrap.classList.remove( openClass );
130 const btn = wrap.querySelector( toggleSelector );
131 if ( btn ) {
132 btn.setAttribute( 'aria-expanded', 'false' );
133 }
134 } );
135 };
136
137 document.addEventListener( 'click', ( e ) => {
138 const target = e.target;
139 const toggleBtn = target.closest( toggleSelector );
140
141 if ( toggleBtn ) {
142 e.preventDefault();
143 e.stopPropagation();
144
145 const wrap = toggleBtn.closest( wrapSelector );
146 if ( ! wrap ) {
147 return;
148 }
149
150 const willOpen = ! wrap.classList.contains( openClass );
151 closeAll( wrap );
152 wrap.classList.toggle( openClass, willOpen );
153 toggleBtn.setAttribute(
154 'aria-expanded',
155 willOpen ? 'true' : 'false',
156 );
157 return;
158 }
159
160 if ( ! target.closest( wrapSelector ) ) {
161 closeAll();
162 }
163
164 // New expand item
165 const elActionExpands = document.querySelectorAll(
166 '.lp-cb-item-action-expand',
167 );
168 if ( target.closest( '.lp-cb-item-action-expand-toggle' ) ) {
169 const elParentAction = target.closest( '.lp-cb-item-action-wrap' );
170 const elActionExpand = elParentAction.querySelector(
171 '.lp-cb-item-action-expand',
172 );
173
174 elActionExpand.classList.toggle( lpUtils.lpClassName.hidden );
175
176 // Close all
177 elActionExpands.forEach( ( elActionExpandCheck ) => {
178 if ( elActionExpandCheck !== elActionExpand ) {
179 elActionExpandCheck.classList.add( lpUtils.lpClassName.hidden );
180 }
181 } );
182 }
183
184 if ( ! target.closest( '.lp-cb-item-action-expand-toggle' ) ) {
185 elActionExpands.forEach( ( elActionExpandCheck ) => {
186 elActionExpandCheck.classList.add(
187 lpUtils.lpClassName.hidden,
188 );
189 } );
190 }
191 // End new expand item
192
193 // Item action
194 if ( target.closest( '.lp-cb-item-action' ) ) {
195 const elAction = target.closest( '.lp-cb-item-action' );
196 const elParentActionWrap = elAction.closest(
197 '.lp-cb-item-action-wrap',
198 );
199 const elParentActionExpand = elParentActionWrap.querySelector(
200 '.lp-cb-item-action-expand-toggle',
201 );
202 const dataSend = JSON.parse( elAction.dataset.send );
203
204 lpUtils.lpSetLoadingEl( elParentActionExpand, 1 );
205 const svg = elParentActionExpand.querySelector( 'svg' );
206 //lpUtils.lpShowHideEl( svg, 0 );
207
208 // Ajax to generate prompt
209 const callBack = {
210 success: ( response ) => {
211 const { message, status, data } = response;
212
213 lpToastify.show( message, status );
214
215 if ( status === 'success' ) {
216 if ( data.redirect_url ) {
217 window.location.href = data.redirect_url;
218 } else if ( data.html ) {
219 const elParentLi = elAction.closest( 'li.course' );
220 elParentLi.outerHTML = data.html;
221 } else if ( dataSend.action_type === 'delete' ) {
222 const elParentLi = elAction.closest( 'li.course' );
223 elParentLi.remove();
224 }
225 }
226 },
227 error: ( error ) => {
228 lpToastify.show( error, 'error' );
229 },
230 completed: () => {
231 lpUtils.lpSetLoadingEl( elParentActionExpand, 0 );
232 },
233 };
234
235 window.lpAJAXG.fetchAJAX( dataSend, callBack );
236 }
237 // End item action
238 } );
239
240 document.addEventListener( 'keydown', ( e ) => {
241 if ( e.key === 'Escape' ) {
242 closeAll();
243 }
244 } );
245 };
246
247 // Add loading state to search buttons on form submit (page reload)
248 document.addEventListener( 'submit', ( e ) => {
249 const form = e.target.closest( '.cb-search-form' );
250 if ( ! form ) {
251 return;
252 }
253
254 const searchBtn = form.querySelector( '.cb-search-btn' );
255 if ( searchBtn ) {
256 searchBtn.classList.add( 'loading' );
257 searchBtn.disabled = true;
258 }
259 } );
260
261 // Initialize components
262 initBuilderComponents();
263
264 // Events
265 document.addEventListener( 'click', ( e ) => {
266 try {
267 initElsTomSelect();
268 } catch ( e ) {
269 console.warn( 'Error initializing TomSelect:', e );
270 }
271 } );
272
273 document.addEventListener( 'DOMContentLoaded', () => {
274 // Sure that the TomSelect is loaded if listener can't find elements.
275 try {
276 initElsTomSelect();
277 } catch ( e ) {
278 console.warn( 'Error initializing TomSelect on DOMContentLoaded:', e );
279 }
280
281 // Initialize BuilderMaterial for Course Builder Settings tab Material
282 try {
283 initBuilderMaterialForCourseSettings();
284 } catch ( e ) {
285 console.error( 'Error initializing BuilderMaterial:', e );
286 }
287 } );
288
289 // Use lpOnElementReady safely
290 if ( Utils?.lpOnElementReady ) {
291 Utils.lpOnElementReady( 'select.lp-tom-select', () => {
292 try {
293 initElsTomSelect();
294 } catch ( e ) {
295 console.warn( 'Error initializing TomSelect:', e );
296 }
297 } );
298 }
299
300 window.lpFindTomSelect = initElsTomSelect;
301
302 /**
303 * Initialize BuilderMaterial for Course Builder Settings tab Material
304 */
305 function initBuilderMaterialForCourseSettings() {
306 const initializedContainers = new WeakSet();
307
308 // Listen for tab clicks in Course Settings using event delegation
309 document.addEventListener( 'click', ( e ) => {
310 const target = e.target.closest( 'ul.lp-meta-box__course-tab__tabs a' );
311
312 if ( ! target ) {
313 return;
314 }
315
316 const targetPanel = target.getAttribute( 'href' );
317
318 // Check if Material tab is clicked
319 if ( targetPanel && targetPanel.includes( 'material' ) ) {
320 // Wait for DOM to update
321 setTimeout( () => {
322 try {
323 const materialContainer = document.querySelector(
324 targetPanel + ' #lp-material-container'
325 );
326 if ( materialContainer && ! initializedContainers.has( materialContainer ) ) {
327 // Mark as initialized to prevent multiple instances
328 initializedContainers.add( materialContainer );
329 // Initialize BuilderMaterial
330 new BuilderMaterial( materialContainer );
331 }
332 } catch ( e ) {
333 console.error( 'Error initializing BuilderMaterial:', e );
334 }
335 }, 100 );
336 }
337 } );
338 }
339