PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.2
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.2
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 / builder-lesson / builder-edit-lesson.js

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

832 lines 23.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import * as lpUtils from 'lpAssetsJsPath/utils.js';
2 import * as lpToastify from 'lpAssetsJsPath/lpToastify.js';
3
4 export class BuilderEditLesson {
5 constructor() {
6 // Events use document-level event delegation, so always register them
7 // The page context check happens in individual handlers via target.closest()
8 this.init();
9 }
10
11 static selectors = {
12 // Context selector - indicates we're on lesson edit page
13 elDataLesson: '.cb-section__lesson-edit',
14 // Shared header action buttons (generic selectors)
15 elBtnMainAction: '.cb-btn-main-action',
16 elBtnUpdate: '.cb-btn-update, .cb-btn-publish',
17 elBtnDraft: '.cb-btn-darft, .cb-dropdown-item[data-status="draft"]',
18 elBtnTrash: '.cb-btn-trash',
19 // Status badge
20 elLessonStatus: '.lesson-status',
21 // Form fields
22 idTitle: 'title',
23 idDescEditor: 'lesson_description_editor',
24 elFormSetting: '.lp-form-setting-lesson',
25 // Permalink component
26 elPermalinkDisplay: '.cb-permalink-display',
27 elPermalinkEditor: '.cb-permalink-editor',
28 elPermalinkEditBtn: '.cb-permalink-edit-btn',
29 elPermalinkOkBtn: '.cb-permalink-ok-btn',
30 elPermalinkCancelBtn: '.cb-permalink-cancel-btn',
31 elPermalinkSlugInput: '.cb-permalink-slug-input',
32 elPermalinkUrl: '.cb-permalink-url',
33 elPermalinkBaseUrl: '#cb-permalink-base-url',
34 elPermalinkRoot: '.cb-item-edit-permalink, .cb-course-edit-permalink',
35 elPermalinkPlaceholder: '.cb-item-edit-permalink__placeholder',
36 // Tab handling selectors
37 elCBHorizontalTabs: '.lp-cb-tabs__item',
38 elCBTabPanels: '.lp-cb-tab-panel',
39 // Dropdown selectors
40 elDropdownToggle: '.cb-btn-dropdown-toggle',
41 elDropdownMenu: '.cb-dropdown-menu',
42 elHeaderActionsDropdown: '.cb-header-actions-dropdown',
43 };
44
45 init() {
46 this.initTabs();
47 this.initHeaderActionsDropdown();
48 this.events();
49 }
50
51 /**
52 * Initialize header actions dropdown (toggle behavior)
53 */
54 initHeaderActionsDropdown() {
55 // Close dropdown when clicking outside
56 document.addEventListener( 'click', ( e ) => {
57 const dropdown = document.querySelector( BuilderEditLesson.selectors.elHeaderActionsDropdown );
58 if ( dropdown && ! dropdown.contains( e.target ) ) {
59 const menu = dropdown.querySelector( BuilderEditLesson.selectors.elDropdownMenu );
60 const toggle = dropdown.querySelector( BuilderEditLesson.selectors.elDropdownToggle );
61 if ( menu ) {
62 menu.classList.remove( 'is-open' );
63 }
64 if ( toggle ) {
65 toggle.setAttribute( 'aria-expanded', 'false' );
66 }
67 }
68 } );
69 }
70
71 /**
72 * Handle dropdown toggle click
73 */
74 handleDropdownToggle( args ) {
75 const { target } = args;
76 const toggleBtn = target.closest( BuilderEditLesson.selectors.elDropdownToggle );
77
78 if ( ! toggleBtn ) {
79 return;
80 }
81
82 const dropdown = toggleBtn.closest( BuilderEditLesson.selectors.elHeaderActionsDropdown );
83 if ( ! dropdown ) {
84 return;
85 }
86
87 const menu = dropdown.querySelector( BuilderEditLesson.selectors.elDropdownMenu );
88 if ( menu ) {
89 menu.classList.toggle( 'is-open' );
90 const isOpen = menu.classList.contains( 'is-open' );
91 toggleBtn.setAttribute( 'aria-expanded', isOpen ? 'true' : 'false' );
92 }
93 }
94
95 /**
96 * Initialize horizontal tabs for client-side tab switching
97 */
98 initTabs() {
99 const tabs = document.querySelectorAll( BuilderEditLesson.selectors.elCBHorizontalTabs );
100 if ( tabs.length === 0 ) {
101 return;
102 }
103
104 // Activate first tab by default if none is active
105 const activeTab = document.querySelector( `${ BuilderEditLesson.selectors.elCBHorizontalTabs }.is-active` );
106 if ( ! activeTab && tabs.length > 0 ) {
107 tabs[0].classList.add( 'is-active' );
108 const section = tabs[0].getAttribute( 'data-tab-section' );
109 if ( section ) {
110 const panel = document.querySelector( `${ BuilderEditLesson.selectors.elCBTabPanels }[data-section="${ section }"]` );
111 if ( panel ) {
112 panel.classList.remove( 'lp-hidden' );
113 }
114 }
115 }
116 }
117
118 /**
119 * Handle horizontal tab click for client-side tab switching
120 */
121 handleTabClick( args ) {
122 const { e, target } = args;
123 const tabLink = target.closest( BuilderEditLesson.selectors.elCBHorizontalTabs );
124
125 if ( ! tabLink ) {
126 return;
127 }
128
129 e.preventDefault();
130
131 const section = tabLink.getAttribute( 'data-tab-section' );
132 if ( ! section ) {
133 return;
134 }
135
136 // Update active tab
137 const allTabs = document.querySelectorAll( BuilderEditLesson.selectors.elCBHorizontalTabs );
138 allTabs.forEach( tab => tab.classList.remove( 'is-active' ) );
139 tabLink.classList.add( 'is-active' );
140
141 // Show/hide panels
142 const allPanels = document.querySelectorAll( BuilderEditLesson.selectors.elCBTabPanels );
143 allPanels.forEach( panel => {
144 if ( panel.getAttribute( 'data-section' ) === section ) {
145 panel.classList.remove( 'lp-hidden' );
146 } else {
147 panel.classList.add( 'lp-hidden' );
148 }
149 } );
150 }
151
152 events() {
153 if ( BuilderEditLesson._loadedEvents ) {
154 return;
155 }
156 BuilderEditLesson._loadedEvents = true;
157
158 lpUtils.eventHandlers( 'click', [
159 {
160 selector: BuilderEditLesson.selectors.elBtnMainAction,
161 class: this,
162 callBack: this.updateLesson.name,
163 },
164 {
165 selector: BuilderEditLesson.selectors.elBtnDraft,
166 class: this,
167 callBack: this.saveDraftLesson.name,
168 },
169 {
170 selector: BuilderEditLesson.selectors.elBtnTrash,
171 class: this,
172 callBack: this.trashLesson.name,
173 },
174 {
175 selector: BuilderEditLesson.selectors.elCBHorizontalTabs,
176 class: this,
177 callBack: this.handleTabClick.name,
178 },
179 {
180 selector: BuilderEditLesson.selectors.elDropdownToggle,
181 class: this,
182 callBack: this.handleDropdownToggle.name,
183 },
184 {
185 selector: BuilderEditLesson.selectors.elPermalinkEditBtn,
186 class: this,
187 callBack: this.handlePermalinkEdit.name,
188 },
189 {
190 selector: BuilderEditLesson.selectors.elPermalinkOkBtn,
191 class: this,
192 callBack: this.handlePermalinkOk.name,
193 },
194 {
195 selector: BuilderEditLesson.selectors.elPermalinkCancelBtn,
196 class: this,
197 callBack: this.handlePermalinkCancel.name,
198 },
199 ] );
200 }
201
202 /**
203 * Validate title is not empty before update
204 * @return {boolean} True if valid, false if invalid
205 */
206 validateTitleBeforeUpdate() {
207 const titleInput = document.getElementById( BuilderEditLesson.selectors.idTitle );
208 if ( ! titleInput ) {
209 return true;
210 }
211
212 const title = titleInput.value.trim();
213 if ( ! title ) {
214 lpToastify.show( 'Lesson title is required.', 'error' );
215 titleInput.focus();
216 return false;
217 }
218 return true;
219 }
220
221 /**
222 * Update action button text after status change
223 * @param {string} newStatus - The new lesson status
224 */
225 updateActionButtons( newStatus ) {
226 const elBtnUpdate = document.querySelector( BuilderEditLesson.selectors.elBtnMainAction );
227 if ( ! elBtnUpdate ) {
228 return;
229 }
230
231 const titleUpdate = elBtnUpdate.getAttribute( 'data-title-update' ) || 'Update';
232 const titlePublish = elBtnUpdate.getAttribute( 'data-title-publish' ) || 'Publish';
233
234 if ( newStatus === 'publish' ) {
235 elBtnUpdate.textContent = titleUpdate;
236 } else {
237 elBtnUpdate.textContent = titlePublish;
238 }
239 }
240
241 /**
242 * Check if we're in lesson edit context
243 * @return {boolean}
244 */
245 isLessonContext() {
246 return !! document.querySelector( BuilderEditLesson.selectors.elDataLesson );
247 }
248
249 slugify( str ) {
250 return ( str || '' )
251 .toString()
252 .normalize( 'NFD' )
253 .replace( /[\u0300-\u036f]/g, '' )
254 .replace( /đ/g, 'd' )
255 .replace( /Đ/g, 'D' )
256 .toLowerCase()
257 .replace( /\s+/g, '-' )
258 .replace( /[^\w-]+/g, '' )
259 .replace( /--+/g, '-' )
260 .replace( /^-+/, '' )
261 .replace( /-+$/, '' );
262 }
263
264 buildPermalinkDisplayUrl( baseUrl = '', slug = '', fallbackUrl = '' ) {
265 const normalizedBaseUrl = typeof baseUrl === 'string' ? baseUrl : '';
266 const normalizedSlug = typeof slug === 'string' ? slug.trim() : '';
267
268 if ( normalizedBaseUrl && normalizedSlug ) {
269 return `${ normalizedBaseUrl }${ normalizedSlug }`;
270 }
271
272 return typeof fallbackUrl === 'string' ? fallbackUrl : '';
273 }
274
275 getPermalinkRoot( target = null ) {
276 if ( target?.closest ) {
277 const fromTarget = target.closest( BuilderEditLesson.selectors.elPermalinkRoot );
278 if ( fromTarget ) {
279 return fromTarget;
280 }
281 }
282
283 return document.querySelector(
284 `.cb-section__lesson-edit ${ BuilderEditLesson.selectors.elPermalinkRoot }`
285 );
286 }
287
288 isDescriptionEditorInCodeMode() {
289 const editorId = BuilderEditLesson.selectors.idDescEditor;
290 const wrapper =
291 document.getElementById( `wp-${ editorId }-wrap` ) ||
292 document.getElementById( `${ editorId }-wrap` );
293 if ( wrapper?.classList.contains( 'html-active' ) ) {
294 return true;
295 }
296
297 const editor = typeof tinymce !== 'undefined' ? tinymce.get( editorId ) : null;
298
299 return !! ( editor?.isHidden && editor.isHidden() );
300 }
301
302 getDescriptionContentForUpdate() {
303 const editorId = BuilderEditLesson.selectors.idDescEditor;
304 const descEditor = document.getElementById( editorId );
305
306 if ( this.isDescriptionEditorInCodeMode() ) {
307 return descEditor ? descEditor.value : '';
308 }
309
310 if ( typeof tinymce !== 'undefined' ) {
311 const editor = tinymce.get( editorId );
312 if ( editor ) {
313 return editor.getContent();
314 }
315 }
316
317 return descEditor ? descEditor.value : '';
318 }
319
320 showPermalinkUnavailable( permalinkRoot, message = '' ) {
321 if ( ! permalinkRoot ) {
322 return;
323 }
324
325 const label =
326 permalinkRoot.querySelector( '.cb-item-edit-permalink__label' ) ||
327 permalinkRoot.querySelector( '.cb-permalink-label' );
328 const display = permalinkRoot.querySelector( BuilderEditLesson.selectors.elPermalinkDisplay );
329 const editor = permalinkRoot.querySelector( BuilderEditLesson.selectors.elPermalinkEditor );
330 let placeholder = permalinkRoot.querySelector(
331 BuilderEditLesson.selectors.elPermalinkPlaceholder
332 );
333
334 if ( ! placeholder ) {
335 placeholder = document.createElement( 'span' );
336 placeholder.className = 'cb-item-edit-permalink__placeholder';
337
338 if ( label ) {
339 label.insertAdjacentElement( 'afterend', placeholder );
340 } else {
341 permalinkRoot.prepend( placeholder );
342 }
343 }
344
345 placeholder.textContent =
346 message || 'Permalink is only available if the item is already assigned to a course.';
347 placeholder.classList.remove( 'lp-hidden' );
348
349 if ( display ) {
350 display.classList.add( 'lp-hidden' );
351 }
352
353 if ( editor ) {
354 editor.classList.add( 'lp-hidden' );
355 }
356 }
357
358 getLessonDataForUpdate() {
359 const data = {};
360
361 const wrapperEl = document.querySelector( BuilderEditLesson.selectors.elDataLesson );
362 data.lesson_id = wrapperEl ? parseInt( wrapperEl.dataset.lessonId ) || 0 : 0;
363
364 const titleInput = document.getElementById( BuilderEditLesson.selectors.idTitle );
365 data.lesson_title = titleInput ? titleInput.value : '';
366
367 data.lesson_description = this.getDescriptionContentForUpdate();
368
369 const permalinkInput = document.querySelector(
370 `input[name="lesson_permalink"], #lesson_permalink, ${ BuilderEditLesson.selectors.elPermalinkSlugInput }`
371 );
372 if ( permalinkInput && permalinkInput.value ) {
373 data.lesson_permalink = permalinkInput.value;
374 }
375
376 const elFormSetting = document.querySelector( BuilderEditLesson.selectors.elFormSetting );
377
378 if ( elFormSetting ) {
379 data.lesson_settings = true;
380 const formElements = elFormSetting.querySelectorAll( 'input, select, textarea' );
381
382 formElements.forEach( ( element ) => {
383 const name = element.name || element.id;
384
385 if ( ! name || name === 'learnpress_meta_box_nonce' || name === '_wp_http_referer' ) {
386 return;
387 }
388
389 if ( element.type === 'checkbox' ) {
390 const fieldName = name.replace( '[]', '' );
391 if ( ! data.hasOwnProperty( fieldName ) ) {
392 data[ fieldName ] = element.checked ? 'yes' : 'no';
393 }
394 } else if ( element.type === 'radio' ) {
395 if ( element.checked ) {
396 const fieldName = name.replace( '[]', '' );
397 data[ fieldName ] = element.value;
398 }
399 } else if ( element.type === 'file' ) {
400 const fieldName = name.replace( '[]', '' );
401 if ( element.files && element.files.length > 0 ) {
402 data[ fieldName ] = element.files;
403 }
404 } else {
405 const fieldName = name.replace( '[]', '' );
406
407 if ( name.endsWith( '[]' ) ) {
408 if ( ! data.hasOwnProperty( fieldName ) ) {
409 data[ fieldName ] = [];
410 }
411
412 if ( Array.isArray( data[ fieldName ] ) ) {
413 data[ fieldName ].push( element.value );
414 }
415 } else {
416 if ( ! data.hasOwnProperty( fieldName ) ) {
417 data[ fieldName ] = element.value;
418 }
419 }
420 }
421 } );
422
423 Object.keys( data ).forEach( ( key ) => {
424 if ( Array.isArray( data[ key ] ) ) {
425 data[ key ] = data[ key ].join( ',' );
426 }
427 } );
428 }
429
430 return data;
431 }
432
433 updatePermalinkUIAfterSave( data = {} ) {
434 const permalinkRoot = this.getPermalinkRoot();
435 if ( ! permalinkRoot ) {
436 return;
437 }
438
439 const slugInput = permalinkRoot.querySelector( BuilderEditLesson.selectors.elPermalinkSlugInput );
440 const urlLink = permalinkRoot.querySelector( BuilderEditLesson.selectors.elPermalinkUrl );
441 const baseUrlInput = permalinkRoot.querySelector( BuilderEditLesson.selectors.elPermalinkBaseUrl );
442 const display = permalinkRoot.querySelector( BuilderEditLesson.selectors.elPermalinkDisplay );
443 const placeholder = permalinkRoot.querySelector(
444 BuilderEditLesson.selectors.elPermalinkPlaceholder
445 );
446 const permalinkDisplayUrl = this.buildPermalinkDisplayUrl(
447 baseUrlInput ? baseUrlInput.value : '',
448 data?.lesson_slug,
449 data?.lesson_permalink
450 );
451
452 if ( slugInput && data?.lesson_slug ) {
453 slugInput.value = data.lesson_slug;
454 slugInput.dataset.originalValue = data.lesson_slug;
455 }
456
457 const shouldShowUnavailable =
458 data?.permalink_available === false ||
459 data?.status === 'draft' ||
460 data?.status === 'trash' ||
461 ! data?.lesson_permalink;
462
463 if ( shouldShowUnavailable ) {
464 this.showPermalinkUnavailable( permalinkRoot, data?.permalink_notice );
465 return;
466 }
467
468 if ( placeholder ) {
469 placeholder.classList.add( 'lp-hidden' );
470 }
471
472 if ( display ) {
473 display.classList.remove( 'lp-hidden' );
474 }
475
476 if ( urlLink && data?.lesson_permalink ) {
477 urlLink.href = data.lesson_permalink;
478 urlLink.textContent = permalinkDisplayUrl || data.lesson_permalink;
479 } else if ( urlLink && permalinkDisplayUrl ) {
480 urlLink.textContent = permalinkDisplayUrl;
481 }
482 }
483
484 handlePermalinkEdit( args ) {
485 if ( ! this.isLessonContext() ) {
486 return;
487 }
488
489 const { e, target } = args;
490 if ( e ) e.preventDefault();
491
492 const trigger = target?.closest( BuilderEditLesson.selectors.elPermalinkEditBtn );
493 const lessonRoot = trigger?.closest( BuilderEditLesson.selectors.elDataLesson ) ||
494 document.querySelector( BuilderEditLesson.selectors.elDataLesson );
495
496 if ( ! lessonRoot ) {
497 return;
498 }
499
500 const display = lessonRoot.querySelector( BuilderEditLesson.selectors.elPermalinkDisplay );
501 const editor = lessonRoot.querySelector( BuilderEditLesson.selectors.elPermalinkEditor );
502 const input = lessonRoot.querySelector( BuilderEditLesson.selectors.elPermalinkSlugInput );
503
504 if ( ! display || ! editor || ! input ) {
505 return;
506 }
507
508 input.dataset.originalValue = input.value;
509 display.classList.add( 'lp-hidden' );
510 editor.classList.remove( 'lp-hidden' );
511 input.focus();
512 input.select();
513 }
514
515 handlePermalinkOk( args ) {
516 if ( ! this.isLessonContext() ) {
517 return;
518 }
519
520 const { e, target } = args;
521 if ( e ) e.preventDefault();
522
523 const trigger = target?.closest( BuilderEditLesson.selectors.elPermalinkOkBtn );
524 const lessonRoot = trigger?.closest( BuilderEditLesson.selectors.elDataLesson ) ||
525 document.querySelector( BuilderEditLesson.selectors.elDataLesson );
526
527 if ( ! lessonRoot ) {
528 return;
529 }
530
531 const display = lessonRoot.querySelector( BuilderEditLesson.selectors.elPermalinkDisplay );
532 const editor = lessonRoot.querySelector( BuilderEditLesson.selectors.elPermalinkEditor );
533 const input = lessonRoot.querySelector( BuilderEditLesson.selectors.elPermalinkSlugInput );
534 const urlLink = lessonRoot.querySelector( BuilderEditLesson.selectors.elPermalinkUrl );
535 const baseUrlInput = lessonRoot.querySelector( BuilderEditLesson.selectors.elPermalinkBaseUrl );
536
537 if ( ! display || ! editor || ! input || ! urlLink ) {
538 return;
539 }
540
541 let newSlug = this.slugify( input.value.trim() );
542 if ( ! newSlug ) {
543 newSlug = input.dataset.originalValue || 'lesson';
544 }
545
546 input.value = newSlug;
547 const baseUrl = baseUrlInput ? baseUrlInput.value : '';
548 const newUrl = this.buildPermalinkDisplayUrl( baseUrl, newSlug, urlLink.textContent || '' );
549
550 // Keep href as the current saved link and only update display text.
551 urlLink.textContent = newUrl;
552 editor.classList.add( 'lp-hidden' );
553 display.classList.remove( 'lp-hidden' );
554 }
555
556 handlePermalinkCancel( args ) {
557 if ( ! this.isLessonContext() ) {
558 return;
559 }
560
561 const { e, target } = args;
562 if ( e ) e.preventDefault();
563
564 const trigger = target?.closest( BuilderEditLesson.selectors.elPermalinkCancelBtn );
565 const lessonRoot = trigger?.closest( BuilderEditLesson.selectors.elDataLesson ) ||
566 document.querySelector( BuilderEditLesson.selectors.elDataLesson );
567
568 if ( ! lessonRoot ) {
569 return;
570 }
571
572 const display = lessonRoot.querySelector( BuilderEditLesson.selectors.elPermalinkDisplay );
573 const editor = lessonRoot.querySelector( BuilderEditLesson.selectors.elPermalinkEditor );
574 const input = lessonRoot.querySelector( BuilderEditLesson.selectors.elPermalinkSlugInput );
575
576 if ( ! display || ! editor || ! input ) {
577 return;
578 }
579
580 input.value = input.dataset.originalValue || '';
581 editor.classList.add( 'lp-hidden' );
582 display.classList.remove( 'lp-hidden' );
583 }
584
585 updateLesson( args ) {
586 // Context check: only handle if on lesson edit page
587 if ( ! this.isLessonContext() ) {
588 return;
589 }
590
591 const { target } = args;
592 const elBtnUpdateLesson = target.closest( BuilderEditLesson.selectors.elBtnMainAction );
593
594 if ( ! elBtnUpdateLesson ) {
595 return;
596 }
597
598 // Validate title before update
599 if ( ! this.validateTitleBeforeUpdate() ) {
600 return;
601 }
602
603 lpUtils.lpSetLoadingEl( elBtnUpdateLesson, 1 );
604
605 const lessonData = this.getLessonDataForUpdate();
606
607 const targetStatus = elBtnUpdateLesson.dataset.status || 'publish';
608
609 const dataSend = {
610 ...lessonData,
611 action: 'builder_update_lesson',
612 args: {
613 id_url: 'builder-update-lesson',
614 },
615 lesson_status: targetStatus,
616 };
617
618 if ( typeof lpLessonBuilder !== 'undefined' && lpLessonBuilder.nonce ) {
619 dataSend.nonce = lpLessonBuilder.nonce;
620 }
621
622 const callBack = {
623 success: ( response ) => {
624 const { status, message, data } = response;
625 lpToastify.show( message, status );
626
627 if ( status === 'success' ) {
628 // Update action button text
629 this.updateActionButtons( targetStatus );
630
631 if ( data?.lesson_id_new ) {
632 const currentUrl = window.location.href;
633 window.location.href = currentUrl.replace( /post-new\/?/, `${ data.lesson_id_new }/` );
634 }
635
636 if ( data?.status ) {
637 const elStatus = document.querySelector( BuilderEditLesson.selectors.elLessonStatus );
638 if ( elStatus ) {
639 elStatus.className = 'lesson-status ' + data.status;
640 elStatus.textContent = data.status;
641 }
642 if ( data.status === 'trash' || data.status === 'draft' ) {
643 const curriculumItem = document.querySelector( `.lesson-item[data-lesson-id="${lessonData.lesson_id}"]` );
644 if ( curriculumItem ) {
645 const elCurriculumLesson = curriculumItem.closest( '.lesson' );
646 if ( elCurriculumLesson ) {
647 elCurriculumLesson.remove();
648 }
649 }
650 }
651 }
652
653 this.updatePermalinkUIAfterSave( data );
654
655 // Reset form state to prevent "leave site" warning
656 document.dispatchEvent( new CustomEvent( 'lp-course-builder-saved' ) );
657 }
658 },
659 error: ( error ) => {
660 lpToastify.show( error.message || error, 'error' );
661 },
662 completed: () => {
663 lpUtils.lpSetLoadingEl( elBtnUpdateLesson, 0 );
664 },
665 };
666
667 window.lpAJAXG.fetchAJAX( dataSend, callBack );
668 }
669
670 saveDraftLesson( args ) {
671 // Context check: only handle if on lesson edit page
672 if ( ! this.isLessonContext() ) {
673 return;
674 }
675
676 const { target } = args;
677 const elBtnDraftLesson = target.closest( BuilderEditLesson.selectors.elBtnDraft );
678
679 if ( ! elBtnDraftLesson ) {
680 return;
681 }
682
683 // Check if published to show confirm unpublish modal
684 const statusEl = document.querySelector( BuilderEditLesson.selectors.elLessonStatus );
685 const isPublished = statusEl && statusEl.classList.contains( 'publish' );
686 if ( isPublished ) {
687 const confirmMsg = elBtnDraftLesson.dataset.confirmUnpublish || 'Saving as draft will unpublish this item. Are you sure?';
688 if ( ! confirm( confirmMsg ) ) {
689 return;
690 }
691 }
692
693 // Validate title before saving draft
694 if ( ! this.validateTitleBeforeUpdate() ) {
695 return;
696 }
697
698 lpUtils.lpSetLoadingEl( elBtnDraftLesson, 1 );
699
700 const lessonData = this.getLessonDataForUpdate();
701
702 const dataSend = {
703 ...lessonData,
704 action: 'builder_update_lesson',
705 args: {
706 id_url: 'builder-update-lesson',
707 },
708 lesson_status: 'draft',
709 };
710
711 if ( typeof lpLessonBuilder !== 'undefined' && lpLessonBuilder.nonce ) {
712 dataSend.nonce = lpLessonBuilder.nonce;
713 }
714
715 const callBack = {
716 success: ( response ) => {
717 const { status, message, data } = response;
718 lpToastify.show( message, status );
719
720 if ( status === 'success' ) {
721 // Update action button text
722 this.updateActionButtons( 'draft' );
723
724 if ( data?.lesson_id_new ) {
725 const currentUrl = window.location.href;
726 window.location.href = currentUrl.replace( /post-new\/?/, `${ data.lesson_id_new }/` );
727 }
728
729 if ( data?.status ) {
730 const elStatus = document.querySelector( BuilderEditLesson.selectors.elLessonStatus );
731 if ( elStatus ) {
732 elStatus.className = 'lesson-status ' + data.status;
733 elStatus.textContent = data.status;
734 }
735 if ( data.status === 'trash' || data.status === 'draft' ) {
736 const curriculumItem = document.querySelector( `.lesson-item[data-lesson-id="${lessonData.lesson_id}"]` );
737 if ( curriculumItem ) {
738 const elCurriculumLesson = curriculumItem.closest( '.lesson' );
739 if ( elCurriculumLesson ) {
740 elCurriculumLesson.remove();
741 }
742 }
743 }
744 }
745
746 this.updatePermalinkUIAfterSave( data );
747
748 // Reset form state to prevent "leave site" warning
749 document.dispatchEvent( new CustomEvent( 'lp-course-builder-saved' ) );
750 }
751 },
752 error: ( error ) => {
753 lpToastify.show( error.message || error, 'error' );
754 },
755 completed: () => {
756 lpUtils.lpSetLoadingEl( elBtnDraftLesson, 0 );
757 },
758 };
759
760 window.lpAJAXG.fetchAJAX( dataSend, callBack );
761 }
762
763 trashLesson( args ) {
764 // Context check: only handle if on lesson edit page
765 if ( ! this.isLessonContext() ) {
766 return;
767 }
768
769 const { target } = args;
770 const elBtnTrashLesson = target.closest( BuilderEditLesson.selectors.elBtnTrash );
771
772 if ( ! elBtnTrashLesson ) {
773 return;
774 }
775
776 if ( ! confirm( 'Are you sure you want to trash this lesson?' ) ) {
777 return;
778 }
779
780 lpUtils.lpSetLoadingEl( elBtnTrashLesson, 1 );
781
782 const lessonData = this.getLessonDataForUpdate();
783 const dataSend = {
784 action: 'move_trash_lesson',
785 args: {
786 id_url: 'move-trash-lesson',
787 },
788 lesson_id: lessonData.lesson_id,
789 };
790
791 const callBack = {
792 success: ( response ) => {
793 const { status, message, data } = response;
794 lpToastify.show( message, status );
795
796 if ( status === 'success' ) {
797 if ( data?.redirect_url ) {
798 window.location.href = data.redirect_url;
799 }
800
801 if ( data?.status ) {
802 const elStatus = document.querySelector( BuilderEditLesson.selectors.elLessonStatus );
803 if ( elStatus ) {
804 elStatus.className = 'lesson-status ' + data.status;
805 elStatus.textContent = data.status;
806 }
807 if ( data.status === 'trash' || data.status === 'draft' ) {
808 const curriculumItem = document.querySelector( `.lesson-item[data-lesson-id="${lessonData.lesson_id}"]` );
809 if ( curriculumItem ) {
810 const elCurriculumLesson = curriculumItem.closest( '.lesson' );
811 if ( elCurriculumLesson ) {
812 elCurriculumLesson.remove();
813 }
814 }
815 }
816 }
817
818 this.updatePermalinkUIAfterSave( data );
819 }
820 },
821 error: ( error ) => {
822 lpToastify.show( error.message || error, 'error' );
823 },
824 completed: () => {
825 lpUtils.lpSetLoadingEl( elBtnTrashLesson, 0 );
826 },
827 };
828
829 window.lpAJAXG.fetchAJAX( dataSend, callBack );
830 }
831 }
832