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 / builder-lesson / builder-edit-lesson.js

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

808 lines 23.2 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 showPermalinkUnavailable( permalinkRoot, message = '' ) {
289 if ( ! permalinkRoot ) {
290 return;
291 }
292
293 const label =
294 permalinkRoot.querySelector( '.cb-item-edit-permalink__label' ) ||
295 permalinkRoot.querySelector( '.cb-permalink-label' );
296 const display = permalinkRoot.querySelector( BuilderEditLesson.selectors.elPermalinkDisplay );
297 const editor = permalinkRoot.querySelector( BuilderEditLesson.selectors.elPermalinkEditor );
298 let placeholder = permalinkRoot.querySelector(
299 BuilderEditLesson.selectors.elPermalinkPlaceholder
300 );
301
302 if ( ! placeholder ) {
303 placeholder = document.createElement( 'span' );
304 placeholder.className = 'cb-item-edit-permalink__placeholder';
305
306 if ( label ) {
307 label.insertAdjacentElement( 'afterend', placeholder );
308 } else {
309 permalinkRoot.prepend( placeholder );
310 }
311 }
312
313 placeholder.textContent =
314 message || 'Permalink is only available if the item is already assigned to a course.';
315 placeholder.classList.remove( 'lp-hidden' );
316
317 if ( display ) {
318 display.classList.add( 'lp-hidden' );
319 }
320
321 if ( editor ) {
322 editor.classList.add( 'lp-hidden' );
323 }
324 }
325
326 getLessonDataForUpdate() {
327 const data = {};
328
329 const wrapperEl = document.querySelector( BuilderEditLesson.selectors.elDataLesson );
330 data.lesson_id = wrapperEl ? parseInt( wrapperEl.dataset.lessonId ) || 0 : 0;
331
332 const titleInput = document.getElementById( BuilderEditLesson.selectors.idTitle );
333 data.lesson_title = titleInput ? titleInput.value : '';
334
335 const descEditor = document.getElementById( BuilderEditLesson.selectors.idDescEditor );
336 data.lesson_description = descEditor ? descEditor.value : '';
337
338 if ( typeof tinymce !== 'undefined' ) {
339 const editor = tinymce.get( BuilderEditLesson.selectors.idDescEditor );
340 if ( editor ) {
341 data.lesson_description = editor.getContent();
342 }
343 }
344
345 const permalinkInput = document.querySelector(
346 `input[name="lesson_permalink"], #lesson_permalink, ${ BuilderEditLesson.selectors.elPermalinkSlugInput }`
347 );
348 if ( permalinkInput && permalinkInput.value ) {
349 data.lesson_permalink = permalinkInput.value;
350 }
351
352 const elFormSetting = document.querySelector( BuilderEditLesson.selectors.elFormSetting );
353
354 if ( elFormSetting ) {
355 data.lesson_settings = true;
356 const formElements = elFormSetting.querySelectorAll( 'input, select, textarea' );
357
358 formElements.forEach( ( element ) => {
359 const name = element.name || element.id;
360
361 if ( ! name || name === 'learnpress_meta_box_nonce' || name === '_wp_http_referer' ) {
362 return;
363 }
364
365 if ( element.type === 'checkbox' ) {
366 const fieldName = name.replace( '[]', '' );
367 if ( ! data.hasOwnProperty( fieldName ) ) {
368 data[ fieldName ] = element.checked ? 'yes' : 'no';
369 }
370 } else if ( element.type === 'radio' ) {
371 if ( element.checked ) {
372 const fieldName = name.replace( '[]', '' );
373 data[ fieldName ] = element.value;
374 }
375 } else if ( element.type === 'file' ) {
376 const fieldName = name.replace( '[]', '' );
377 if ( element.files && element.files.length > 0 ) {
378 data[ fieldName ] = element.files;
379 }
380 } else {
381 const fieldName = name.replace( '[]', '' );
382
383 if ( name.endsWith( '[]' ) ) {
384 if ( ! data.hasOwnProperty( fieldName ) ) {
385 data[ fieldName ] = [];
386 }
387
388 if ( Array.isArray( data[ fieldName ] ) ) {
389 data[ fieldName ].push( element.value );
390 }
391 } else {
392 if ( ! data.hasOwnProperty( fieldName ) ) {
393 data[ fieldName ] = element.value;
394 }
395 }
396 }
397 } );
398
399 Object.keys( data ).forEach( ( key ) => {
400 if ( Array.isArray( data[ key ] ) ) {
401 data[ key ] = data[ key ].join( ',' );
402 }
403 } );
404 }
405
406 return data;
407 }
408
409 updatePermalinkUIAfterSave( data = {} ) {
410 const permalinkRoot = this.getPermalinkRoot();
411 if ( ! permalinkRoot ) {
412 return;
413 }
414
415 const slugInput = permalinkRoot.querySelector( BuilderEditLesson.selectors.elPermalinkSlugInput );
416 const urlLink = permalinkRoot.querySelector( BuilderEditLesson.selectors.elPermalinkUrl );
417 const baseUrlInput = permalinkRoot.querySelector( BuilderEditLesson.selectors.elPermalinkBaseUrl );
418 const display = permalinkRoot.querySelector( BuilderEditLesson.selectors.elPermalinkDisplay );
419 const placeholder = permalinkRoot.querySelector(
420 BuilderEditLesson.selectors.elPermalinkPlaceholder
421 );
422 const permalinkDisplayUrl = this.buildPermalinkDisplayUrl(
423 baseUrlInput ? baseUrlInput.value : '',
424 data?.lesson_slug,
425 data?.lesson_permalink
426 );
427
428 if ( slugInput && data?.lesson_slug ) {
429 slugInput.value = data.lesson_slug;
430 slugInput.dataset.originalValue = data.lesson_slug;
431 }
432
433 const shouldShowUnavailable =
434 data?.permalink_available === false ||
435 data?.status === 'draft' ||
436 data?.status === 'trash' ||
437 ! data?.lesson_permalink;
438
439 if ( shouldShowUnavailable ) {
440 this.showPermalinkUnavailable( permalinkRoot, data?.permalink_notice );
441 return;
442 }
443
444 if ( placeholder ) {
445 placeholder.classList.add( 'lp-hidden' );
446 }
447
448 if ( display ) {
449 display.classList.remove( 'lp-hidden' );
450 }
451
452 if ( urlLink && data?.lesson_permalink ) {
453 urlLink.href = data.lesson_permalink;
454 urlLink.textContent = permalinkDisplayUrl || data.lesson_permalink;
455 } else if ( urlLink && permalinkDisplayUrl ) {
456 urlLink.textContent = permalinkDisplayUrl;
457 }
458 }
459
460 handlePermalinkEdit( args ) {
461 if ( ! this.isLessonContext() ) {
462 return;
463 }
464
465 const { e, target } = args;
466 if ( e ) e.preventDefault();
467
468 const trigger = target?.closest( BuilderEditLesson.selectors.elPermalinkEditBtn );
469 const lessonRoot = trigger?.closest( BuilderEditLesson.selectors.elDataLesson ) ||
470 document.querySelector( BuilderEditLesson.selectors.elDataLesson );
471
472 if ( ! lessonRoot ) {
473 return;
474 }
475
476 const display = lessonRoot.querySelector( BuilderEditLesson.selectors.elPermalinkDisplay );
477 const editor = lessonRoot.querySelector( BuilderEditLesson.selectors.elPermalinkEditor );
478 const input = lessonRoot.querySelector( BuilderEditLesson.selectors.elPermalinkSlugInput );
479
480 if ( ! display || ! editor || ! input ) {
481 return;
482 }
483
484 input.dataset.originalValue = input.value;
485 display.classList.add( 'lp-hidden' );
486 editor.classList.remove( 'lp-hidden' );
487 input.focus();
488 input.select();
489 }
490
491 handlePermalinkOk( args ) {
492 if ( ! this.isLessonContext() ) {
493 return;
494 }
495
496 const { e, target } = args;
497 if ( e ) e.preventDefault();
498
499 const trigger = target?.closest( BuilderEditLesson.selectors.elPermalinkOkBtn );
500 const lessonRoot = trigger?.closest( BuilderEditLesson.selectors.elDataLesson ) ||
501 document.querySelector( BuilderEditLesson.selectors.elDataLesson );
502
503 if ( ! lessonRoot ) {
504 return;
505 }
506
507 const display = lessonRoot.querySelector( BuilderEditLesson.selectors.elPermalinkDisplay );
508 const editor = lessonRoot.querySelector( BuilderEditLesson.selectors.elPermalinkEditor );
509 const input = lessonRoot.querySelector( BuilderEditLesson.selectors.elPermalinkSlugInput );
510 const urlLink = lessonRoot.querySelector( BuilderEditLesson.selectors.elPermalinkUrl );
511 const baseUrlInput = lessonRoot.querySelector( BuilderEditLesson.selectors.elPermalinkBaseUrl );
512
513 if ( ! display || ! editor || ! input || ! urlLink ) {
514 return;
515 }
516
517 let newSlug = this.slugify( input.value.trim() );
518 if ( ! newSlug ) {
519 newSlug = input.dataset.originalValue || 'lesson';
520 }
521
522 input.value = newSlug;
523 const baseUrl = baseUrlInput ? baseUrlInput.value : '';
524 const newUrl = this.buildPermalinkDisplayUrl( baseUrl, newSlug, urlLink.textContent || '' );
525
526 // Keep href as the current saved link and only update display text.
527 urlLink.textContent = newUrl;
528 editor.classList.add( 'lp-hidden' );
529 display.classList.remove( 'lp-hidden' );
530 }
531
532 handlePermalinkCancel( args ) {
533 if ( ! this.isLessonContext() ) {
534 return;
535 }
536
537 const { e, target } = args;
538 if ( e ) e.preventDefault();
539
540 const trigger = target?.closest( BuilderEditLesson.selectors.elPermalinkCancelBtn );
541 const lessonRoot = trigger?.closest( BuilderEditLesson.selectors.elDataLesson ) ||
542 document.querySelector( BuilderEditLesson.selectors.elDataLesson );
543
544 if ( ! lessonRoot ) {
545 return;
546 }
547
548 const display = lessonRoot.querySelector( BuilderEditLesson.selectors.elPermalinkDisplay );
549 const editor = lessonRoot.querySelector( BuilderEditLesson.selectors.elPermalinkEditor );
550 const input = lessonRoot.querySelector( BuilderEditLesson.selectors.elPermalinkSlugInput );
551
552 if ( ! display || ! editor || ! input ) {
553 return;
554 }
555
556 input.value = input.dataset.originalValue || '';
557 editor.classList.add( 'lp-hidden' );
558 display.classList.remove( 'lp-hidden' );
559 }
560
561 updateLesson( args ) {
562 // Context check: only handle if on lesson edit page
563 if ( ! this.isLessonContext() ) {
564 return;
565 }
566
567 const { target } = args;
568 const elBtnUpdateLesson = target.closest( BuilderEditLesson.selectors.elBtnMainAction );
569
570 if ( ! elBtnUpdateLesson ) {
571 return;
572 }
573
574 // Validate title before update
575 if ( ! this.validateTitleBeforeUpdate() ) {
576 return;
577 }
578
579 lpUtils.lpSetLoadingEl( elBtnUpdateLesson, 1 );
580
581 const lessonData = this.getLessonDataForUpdate();
582
583 const targetStatus = elBtnUpdateLesson.dataset.status || 'publish';
584
585 const dataSend = {
586 ...lessonData,
587 action: 'builder_update_lesson',
588 args: {
589 id_url: 'builder-update-lesson',
590 },
591 lesson_status: targetStatus,
592 };
593
594 if ( typeof lpLessonBuilder !== 'undefined' && lpLessonBuilder.nonce ) {
595 dataSend.nonce = lpLessonBuilder.nonce;
596 }
597
598 const callBack = {
599 success: ( response ) => {
600 const { status, message, data } = response;
601 lpToastify.show( message, status );
602
603 if ( status === 'success' ) {
604 // Update action button text
605 this.updateActionButtons( targetStatus );
606
607 if ( data?.lesson_id_new ) {
608 const currentUrl = window.location.href;
609 window.location.href = currentUrl.replace( /post-new\/?/, `${ data.lesson_id_new }/` );
610 }
611
612 if ( data?.status ) {
613 const elStatus = document.querySelector( BuilderEditLesson.selectors.elLessonStatus );
614 if ( elStatus ) {
615 elStatus.className = 'lesson-status ' + data.status;
616 elStatus.textContent = data.status;
617 }
618 if ( data.status === 'trash' || data.status === 'draft' ) {
619 const curriculumItem = document.querySelector( `.lesson-item[data-lesson-id="${lessonData.lesson_id}"]` );
620 if ( curriculumItem ) {
621 const elCurriculumLesson = curriculumItem.closest( '.lesson' );
622 if ( elCurriculumLesson ) {
623 elCurriculumLesson.remove();
624 }
625 }
626 }
627 }
628
629 this.updatePermalinkUIAfterSave( data );
630
631 // Reset form state to prevent "leave site" warning
632 document.dispatchEvent( new CustomEvent( 'lp-course-builder-saved' ) );
633 }
634 },
635 error: ( error ) => {
636 lpToastify.show( error.message || error, 'error' );
637 },
638 completed: () => {
639 lpUtils.lpSetLoadingEl( elBtnUpdateLesson, 0 );
640 },
641 };
642
643 window.lpAJAXG.fetchAJAX( dataSend, callBack );
644 }
645
646 saveDraftLesson( args ) {
647 // Context check: only handle if on lesson edit page
648 if ( ! this.isLessonContext() ) {
649 return;
650 }
651
652 const { target } = args;
653 const elBtnDraftLesson = target.closest( BuilderEditLesson.selectors.elBtnDraft );
654
655 if ( ! elBtnDraftLesson ) {
656 return;
657 }
658
659 // Check if published to show confirm unpublish modal
660 const statusEl = document.querySelector( BuilderEditLesson.selectors.elLessonStatus );
661 const isPublished = statusEl && statusEl.classList.contains( 'publish' );
662 if ( isPublished ) {
663 const confirmMsg = elBtnDraftLesson.dataset.confirmUnpublish || 'Saving as draft will unpublish this item. Are you sure?';
664 if ( ! confirm( confirmMsg ) ) {
665 return;
666 }
667 }
668
669 // Validate title before saving draft
670 if ( ! this.validateTitleBeforeUpdate() ) {
671 return;
672 }
673
674 lpUtils.lpSetLoadingEl( elBtnDraftLesson, 1 );
675
676 const lessonData = this.getLessonDataForUpdate();
677
678 const dataSend = {
679 ...lessonData,
680 action: 'builder_update_lesson',
681 args: {
682 id_url: 'builder-update-lesson',
683 },
684 lesson_status: 'draft',
685 };
686
687 if ( typeof lpLessonBuilder !== 'undefined' && lpLessonBuilder.nonce ) {
688 dataSend.nonce = lpLessonBuilder.nonce;
689 }
690
691 const callBack = {
692 success: ( response ) => {
693 const { status, message, data } = response;
694 lpToastify.show( message, status );
695
696 if ( status === 'success' ) {
697 // Update action button text
698 this.updateActionButtons( 'draft' );
699
700 if ( data?.lesson_id_new ) {
701 const currentUrl = window.location.href;
702 window.location.href = currentUrl.replace( /post-new\/?/, `${ data.lesson_id_new }/` );
703 }
704
705 if ( data?.status ) {
706 const elStatus = document.querySelector( BuilderEditLesson.selectors.elLessonStatus );
707 if ( elStatus ) {
708 elStatus.className = 'lesson-status ' + data.status;
709 elStatus.textContent = data.status;
710 }
711 if ( data.status === 'trash' || data.status === 'draft' ) {
712 const curriculumItem = document.querySelector( `.lesson-item[data-lesson-id="${lessonData.lesson_id}"]` );
713 if ( curriculumItem ) {
714 const elCurriculumLesson = curriculumItem.closest( '.lesson' );
715 if ( elCurriculumLesson ) {
716 elCurriculumLesson.remove();
717 }
718 }
719 }
720 }
721
722 this.updatePermalinkUIAfterSave( data );
723
724 // Reset form state to prevent "leave site" warning
725 document.dispatchEvent( new CustomEvent( 'lp-course-builder-saved' ) );
726 }
727 },
728 error: ( error ) => {
729 lpToastify.show( error.message || error, 'error' );
730 },
731 completed: () => {
732 lpUtils.lpSetLoadingEl( elBtnDraftLesson, 0 );
733 },
734 };
735
736 window.lpAJAXG.fetchAJAX( dataSend, callBack );
737 }
738
739 trashLesson( args ) {
740 // Context check: only handle if on lesson edit page
741 if ( ! this.isLessonContext() ) {
742 return;
743 }
744
745 const { target } = args;
746 const elBtnTrashLesson = target.closest( BuilderEditLesson.selectors.elBtnTrash );
747
748 if ( ! elBtnTrashLesson ) {
749 return;
750 }
751
752 if ( ! confirm( 'Are you sure you want to trash this lesson?' ) ) {
753 return;
754 }
755
756 lpUtils.lpSetLoadingEl( elBtnTrashLesson, 1 );
757
758 const lessonData = this.getLessonDataForUpdate();
759 const dataSend = {
760 action: 'move_trash_lesson',
761 args: {
762 id_url: 'move-trash-lesson',
763 },
764 lesson_id: lessonData.lesson_id,
765 };
766
767 const callBack = {
768 success: ( response ) => {
769 const { status, message, data } = response;
770 lpToastify.show( message, status );
771
772 if ( status === 'success' ) {
773 if ( data?.redirect_url ) {
774 window.location.href = data.redirect_url;
775 }
776
777 if ( data?.status ) {
778 const elStatus = document.querySelector( BuilderEditLesson.selectors.elLessonStatus );
779 if ( elStatus ) {
780 elStatus.className = 'lesson-status ' + data.status;
781 elStatus.textContent = data.status;
782 }
783 if ( data.status === 'trash' || data.status === 'draft' ) {
784 const curriculumItem = document.querySelector( `.lesson-item[data-lesson-id="${lessonData.lesson_id}"]` );
785 if ( curriculumItem ) {
786 const elCurriculumLesson = curriculumItem.closest( '.lesson' );
787 if ( elCurriculumLesson ) {
788 elCurriculumLesson.remove();
789 }
790 }
791 }
792 }
793
794 this.updatePermalinkUIAfterSave( data );
795 }
796 },
797 error: ( error ) => {
798 lpToastify.show( error.message || error, 'error' );
799 },
800 completed: () => {
801 lpUtils.lpSetLoadingEl( elBtnTrashLesson, 0 );
802 },
803 };
804
805 window.lpAJAXG.fetchAJAX( dataSend, callBack );
806 }
807 }
808