# learnpress/4.4.9/assets/src/js/frontend/course-builder/builder-question/builder-edit-question.js

LearnPress – WordPress LMS Plugin for Create and Sell Online Courses, version 4.4.9. 1,240 lines.

- Page: https://pluginprobe.com/plugins/learnpress/4.4.9/code/assets/src/js/frontend/course-builder/builder-question/builder-edit-question.js
- Raw: https://pluginprobe.com/plugins/learnpress/4.4.9/raw/assets/src/js/frontend/course-builder/builder-question/builder-edit-question.js
- Modified: 2026-09-27T10:32:46+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/learnpress/4.4.9/code/assets/src/js/frontend/course-builder/builder-question/builder-edit-question.js#L10-L20`.

```javascript
import * as lpUtils from 'lpAssetsJsPath/utils.js';
import * as lpToastify from 'lpAssetsJsPath/lpToastify.js';
import SweetAlert from 'sweetalert2';
import { EditQuestion } from 'lpAssetsJsPath/admin/edit-question.js';
import { SWAL_ICON_DUPLICATE, SWAL_ICON_TRASH_DRAFT } from '../swal-icons.js';
import { getFormState } from '../builder-form-state.js';

export class BuilderEditQuestion {
	constructor() {
		this.editQuestion = null;
		// Events use document-level event delegation, so always register them
		// The page context check happens in individual handlers via target.closest()
		this.init();
	}

	static selectors = {
		elDataQuestion: '.cb-section__question-edit',
		elBtnUpdateQuestion: '.cb-btn-update',
		elBtnDraftQuestion: '.cb-btn-draft',
		elBtnDuplicateQuestion: '.cb-btn-duplicate-question',
		elBtnTrashQuestion: '.cb-btn-trash',
		elBtnMainAction: '.cb-btn-main-action',
		elQuestionStatus: '.question-status',
		idTitle: 'title',
		idDescEditor: 'question_description_editor',
		elPublishStatusSelect: '#cb-question-publish-status',
		elFormSetting: '.lp-form-setting-question',
		// Permalink component
		elPermalinkDisplay: '.cb-permalink-display',
		elPermalinkEditor: '.cb-permalink-editor',
		elPermalinkEditBtn: '.cb-permalink-edit-btn',
		elPermalinkOkBtn: '.cb-permalink-ok-btn',
		elPermalinkCancelBtn: '.cb-permalink-cancel-btn',
		elPermalinkSlugInput: '.cb-permalink-slug-input',
		elPermalinkUrl: '.cb-permalink-url',
		elPermalinkBaseUrl: '#cb-permalink-base-url',
		// Question edit selectors
		elEditQuestionWrap: '.lp-edit-question-wrap',
		elQuestionEditMain: '.lp-question-edit-main',
		// Tab handling selectors
		elCBHorizontalTabs: '.lp-cb-tabs__item',
		elCBTabPanels: '.lp-cb-tab-panel',
		// Dropdown selectors
		elDropdownToggle: '.cb-btn-dropdown-toggle',
		elDropdownMenu: '.cb-dropdown-menu',
		elDropdownItem: '.cb-dropdown-item',
		elHeaderActionsDropdown: '.cb-header-actions-dropdown',
		elQuestionActionExpanded: '.course-action-expanded',
	};

	init() {
		this.initQuestionAnswersSettings();
		this.initTabs();
		this.initHeaderActionsDropdown();
		this.syncHeaderActionWithPublishPanel();
		this.events();
	}

	/**
	 * Initialize header actions dropdown (toggle behavior)
	 */
	initHeaderActionsDropdown() {
		// Close dropdown when clicking outside
		document.addEventListener( 'click', ( e ) => {
			const dropdown = document.querySelector(
				BuilderEditQuestion.selectors.elHeaderActionsDropdown
			);
			if ( dropdown && ! dropdown.contains( e.target ) ) {
				const menu = dropdown.querySelector( BuilderEditQuestion.selectors.elDropdownMenu );
				const toggle = dropdown.querySelector( BuilderEditQuestion.selectors.elDropdownToggle );
				if ( menu ) {
					menu.classList.remove( 'is-open' );
				}
				if ( toggle ) {
					toggle.setAttribute( 'aria-expanded', 'false' );
				}
			}
		} );
	}

	/**
	 * Handle dropdown toggle click
	 */
	handleDropdownToggle( args ) {
		if ( this.hasPublishDrivenSingleAction() ) {
			return;
		}

		const { target } = args;
		const toggleBtn = target.closest( BuilderEditQuestion.selectors.elDropdownToggle );

		if ( ! toggleBtn ) {
			return;
		}

		const dropdown = toggleBtn.closest( BuilderEditQuestion.selectors.elHeaderActionsDropdown );
		if ( ! dropdown ) {
			return;
		}

		const menu = dropdown.querySelector( BuilderEditQuestion.selectors.elDropdownMenu );
		if ( menu ) {
			menu.classList.toggle( 'is-open' );
			const isOpen = menu.classList.contains( 'is-open' );
			toggleBtn.setAttribute( 'aria-expanded', isOpen ? 'true' : 'false' );
		}
	}

	/**
	 * Handle dropdown item click (Save Draft, Publish from dropdown menu)
	 */
	handleDropdownItemClick( args ) {
		// Context check: only handle if on question edit page
		if ( ! this.isQuestionContext() ) {
			return;
		}

		if ( this.hasPublishDrivenSingleAction() ) {
			return;
		}

		const { target } = args;
		const dropdownItem = target.closest( BuilderEditQuestion.selectors.elDropdownItem );

		if ( ! dropdownItem ) {
			return;
		}

		// Skip if this is trash button - it has its own handler
		if (
			dropdownItem.classList.contains( 'cb-btn-trash' ) ||
			dropdownItem.classList.contains( 'cb-btn-duplicate-question' )
		) {
			return;
		}

		const status = dropdownItem.dataset.status;
		if ( ! status ) {
			return;
		}

		// Close the dropdown menu
		const menu = dropdownItem.closest( BuilderEditQuestion.selectors.elDropdownMenu );
		if ( menu ) {
			menu.classList.remove( 'is-open' );
		}

		// Save with the specified status
		this.saveQuestionWithStatus( dropdownItem, status );
	}

	/**
	 * Save question with specified status (publish/draft)
	 * @param {HTMLElement} btnEl - The button element that was clicked
	 * @param {string} status - The status to save (publish/draft)
	 */
	async saveQuestionWithStatus( btnEl, status ) {
		const canContinue = await this.confirmUnpublishIfNeeded( status, btnEl );
		if ( ! canContinue ) {
			return;
		}

		// Validate title before saving
		if ( ! this.validateTitleBeforeUpdate() ) {
			return;
		}

		lpUtils.lpSetLoadingEl( btnEl, 1 );

		const questionData = this.getQuestionDataForUpdate();

		const dataSend = {
			...questionData,
			action: 'builder_update_question',
			args: {
				id_url: 'builder-update-question',
			},
			question_status: status,
		};

		if ( typeof lpQuestionBuilder !== 'undefined' && lpQuestionBuilder.nonce ) {
			dataSend.nonce = lpQuestionBuilder.nonce;
		}

		const callBack = {
			success: ( response ) => {
				const { status: respStatus, message, data } = response;
				lpToastify.show( message, respStatus );

				if ( respStatus === 'success' ) {
					// Update action button text
					this.updateActionButtons( data?.status || status );
					this.syncPublishPanelStatus( data?.status || status );
					this.updatePermalinkUIAfterSave( data );

					// Reset form state before potential redirect.
					document.dispatchEvent( new CustomEvent( 'lp-course-builder-saved' ) );

					if ( data?.redirect_url ) {
						window.location.href = data.redirect_url;
					}

					if ( data?.status ) {
						const elStatus = document.querySelector(
							BuilderEditQuestion.selectors.elQuestionStatus
						);
						if ( elStatus ) {
							elStatus.className = 'question-status ' + data.status;
							elStatus.textContent = data.status;
						}

						if ( data.status === 'trash' || data.status === 'draft' ) {
							const curriculumItem = document.querySelector(
								`.question-item[data-question-id="${ questionData.question_id }"]`
							);
							if ( curriculumItem ) {
								const elCurriculumQuestion = curriculumItem.closest( '.question' );
								if ( elCurriculumQuestion ) {
									elCurriculumQuestion.remove();
								}
							}
						}
					}
				}
			},
			error: ( error ) => {
				lpToastify.show( error.message || error, 'error' );
			},
			completed: () => {
				lpUtils.lpSetLoadingEl( btnEl, 0 );
			},
		};

		window.lpAJAXG.fetchAJAX( dataSend, callBack );
	}

	/**
	 * Initialize horizontal tabs for client-side tab switching
	 */
	initTabs() {
		const tabs = document.querySelectorAll( BuilderEditQuestion.selectors.elCBHorizontalTabs );
		if ( tabs.length === 0 ) {
			return;
		}

		// Activate first tab by default if none is active
		const activeTab = document.querySelector(
			`${ BuilderEditQuestion.selectors.elCBHorizontalTabs }.is-active`
		);
		if ( ! activeTab && tabs.length > 0 ) {
			tabs[ 0 ].classList.add( 'is-active' );
			const section = tabs[ 0 ].getAttribute( 'data-tab-section' );
			if ( section ) {
				const panel = document.querySelector(
					`${ BuilderEditQuestion.selectors.elCBTabPanels }[data-section="${ section }"]`
				);
				if ( panel ) {
					panel.classList.remove( 'lp-hidden' );
				}
			}
		}
	}

	/**
	 * Handle horizontal tab click for client-side tab switching
	 */
	handleTabClick( args ) {
		const { e, target } = args;
		const tabLink = target.closest( BuilderEditQuestion.selectors.elCBHorizontalTabs );

		if ( ! tabLink ) {
			return;
		}

		e.preventDefault();

		const section = tabLink.getAttribute( 'data-tab-section' );
		if ( ! section ) {
			return;
		}

		// Update active tab
		const allTabs = document.querySelectorAll( BuilderEditQuestion.selectors.elCBHorizontalTabs );
		allTabs.forEach( ( tab ) => tab.classList.remove( 'is-active' ) );
		tabLink.classList.add( 'is-active' );

		const url = new URL( window.location.href );
		url.searchParams.set( 'tab', section );
		window.history.replaceState( {}, '', url );

		// Show/hide panels
		const allPanels = document.querySelectorAll( BuilderEditQuestion.selectors.elCBTabPanels );
		allPanels.forEach( ( panel ) => {
			if ( panel.getAttribute( 'data-section' ) === section ) {
				panel.classList.remove( 'lp-hidden' );
			} else {
				panel.classList.add( 'lp-hidden' );
			}
		} );
	}

	/**
	 * Initialize Question Answers Settings
	 * This will init EditQuestion class for the question answer management
	 */
	initQuestionAnswersSettings() {
		lpUtils.lpOnElementReady(
			BuilderEditQuestion.selectors.elQuestionEditMain,
			( elQuestionEditMain ) => {
				// Initialize EditQuestion for question answer editing
				if ( ! this.editQuestion ) {
					this.editQuestion = new EditQuestion();
					this.editQuestion.init();
				}

				// Init sortable for question answers
				if ( this.editQuestion ) {
					this.editQuestion.sortAbleQuestionAnswer( elQuestionEditMain );
				}
			}
		);
	}

	/**
	 * Re-initialize when question type changes
	 */
	reinitQuestionHandlers( elQuestionEditMain ) {
		if ( this.editQuestion && elQuestionEditMain ) {
			this.editQuestion.sortAbleQuestionAnswer( elQuestionEditMain );
			this.editQuestion.initTinyMCE();
		}
	}

	/**
	 * Re-initialize for popup context
	 * This is called when popup is opened multiple times to ensure
	 * TinyMCE and other handlers are properly re-initialized
	 *
	 * @param {HTMLElement} container - The popup container element
	 */
	reinit( container ) {
		const elQuestionEditMain = container
			? container.querySelector( BuilderEditQuestion.selectors.elQuestionEditMain )
			: document.querySelector( BuilderEditQuestion.selectors.elQuestionEditMain );

		if ( ! elQuestionEditMain ) {
			return;
		}

		// Re-create EditQuestion instance to ensure fresh initialization
		// This is necessary because TinyMCE instances were destroyed when popup closed
		if ( this.editQuestion ) {
			// Destroy existing TinyMCE instances in the container first
			if ( typeof tinymce !== 'undefined' && container ) {
				const textareas = container.querySelectorAll( 'textarea.lp-meta-box__editor' );
				textareas.forEach( ( textarea ) => {
					const editorId = textarea.id;
					if ( editorId ) {
						const editor = tinymce.get( editorId );
						if ( editor ) {
							editor.remove();
						}
						if ( typeof wp !== 'undefined' && wp.editor && wp.editor.remove ) {
							wp.editor.remove( editorId );
						}
					}
				} );
			}
		}

		// Create fresh EditQuestion instance
		this.editQuestion = new EditQuestion();
		this.editQuestion.init();

		// Re-init sortable and TinyMCE
		this.editQuestion.sortAbleQuestionAnswer( elQuestionEditMain );

		// Use setTimeout to ensure DOM is ready for TinyMCE
		setTimeout( () => {
			if ( this.editQuestion ) {
				this.editQuestion.initTinyMCE();
			}
		}, 100 );
	}

	events() {
		if ( BuilderEditQuestion._loadedEvents ) {
			return;
		}
		BuilderEditQuestion._loadedEvents = true;

		lpUtils.eventHandlers( 'click', [
			{
				selector: BuilderEditQuestion.selectors.elBtnMainAction,
				class: this,
				callBack: this.updateQuestion.name,
			},
			{
				selector: BuilderEditQuestion.selectors.elBtnDraftQuestion,
				class: this,
				callBack: this.saveDraftQuestion.name,
			},
			{
				selector: BuilderEditQuestion.selectors.elBtnDuplicateQuestion,
				class: this,
				callBack: this.duplicateQuestion.name,
			},
			{
				selector: BuilderEditQuestion.selectors.elBtnTrashQuestion,
				class: this,
				callBack: this.trashQuestion.name,
			},
			{
				selector: BuilderEditQuestion.selectors.elCBHorizontalTabs,
				class: this,
				callBack: this.handleTabClick.name,
			},
			{
				selector: BuilderEditQuestion.selectors.elDropdownToggle,
				class: this,
				callBack: this.handleDropdownToggle.name,
			},
			{
				selector: BuilderEditQuestion.selectors.elDropdownItem,
				class: this,
				callBack: this.handleDropdownItemClick.name,
			},
			{
				selector: BuilderEditQuestion.selectors.elPermalinkEditBtn,
				class: this,
				callBack: this.handlePermalinkEdit.name,
			},
			{
				selector: BuilderEditQuestion.selectors.elPermalinkOkBtn,
				class: this,
				callBack: this.handlePermalinkOk.name,
			},
			{
				selector: BuilderEditQuestion.selectors.elPermalinkCancelBtn,
				class: this,
				callBack: this.handlePermalinkCancel.name,
			},
		] );

		lpUtils.eventHandlers( 'change', [
			{
				selector: BuilderEditQuestion.selectors.elPublishStatusSelect,
				class: this,
				callBack: this.handlePublishStatusChange.name,
			},
		] );
	}

	handlePublishStatusChange() {
		this.syncHeaderActionWithPublishPanel();
		getFormState().markAsChanged();
	}

	/**
	 * Validate title is not empty before update
	 * @return {boolean} True if valid, false if invalid
	 */
	validateTitleBeforeUpdate() {
		const titleInput = document.getElementById( BuilderEditQuestion.selectors.idTitle );
		if ( ! titleInput ) {
			return true;
		}

		const title = titleInput.value.trim();
		if ( ! title ) {
			lpToastify.show( 'Question title is required.', 'error' );
			titleInput.focus();
			return false;
		}
		return true;
	}

	/**
	 * Update action buttons after status change, matching course edit logic.
	 * Updates both main button and dropdown items based on new status.
	 * @param {string} newStatus - The new question status
	 */
	updateActionButtons( newStatus ) {
		const dropdown = document.querySelector(
			BuilderEditQuestion.selectors.elHeaderActionsDropdown
		);
		if ( ! dropdown ) return;

		const mainBtn = dropdown.querySelector( '.cb-btn-main-action' );
		const dropdownMenu = dropdown.querySelector( BuilderEditQuestion.selectors.elDropdownMenu );
		if ( ! mainBtn ) return;

		if ( this.hasPublishDrivenSingleAction() ) {
			this.syncHeaderActionWithPublishPanel();
			return;
		}

		if ( ! dropdownMenu ) return;

		// Status configuration for button labels and classes
		const statusConfig = {
			publish: {
				mainLabel: mainBtn.dataset.titleUpdate || 'Update',
				mainClass: 'cb-btn-update',
				mainStatus: 'publish',
				dropdownLabel: mainBtn.dataset.titleDraft || 'Save Draft',
				dropdownClass: 'cb-btn-darft',
				dropdownStatus: 'draft',
				dropdownIcon: 'dashicons-media-default',
			},
			draft: {
				mainLabel: mainBtn.dataset.titleDraft || 'Save Draft',
				mainClass: 'cb-btn-darft',
				mainStatus: 'draft',
				dropdownLabel: mainBtn.dataset.titlePublish || 'Publish',
				dropdownClass: 'cb-btn-publish',
				dropdownStatus: 'publish',
				dropdownIcon: 'dashicons-visibility',
			},
			pending: {
				mainLabel: 'Submit for Review',
				mainClass: 'cb-btn-pending',
				mainStatus: 'pending',
				dropdownLabel: mainBtn.dataset.titleDraft || 'Save Draft',
				dropdownClass: 'cb-btn-darft',
				dropdownStatus: 'draft',
				dropdownIcon: 'dashicons-media-default',
			},
		};

		const config = statusConfig[ newStatus ] || statusConfig.draft;

		// Update main button
		mainBtn.className = `${ config.mainClass } cb-btn-primary cb-btn-main-action`;
		mainBtn.dataset.status = config.mainStatus;
		mainBtn.textContent = config.mainLabel;

		// Update dropdown item (first item, excluding trash)
		const dropdownItems = dropdownMenu.querySelectorAll( '.cb-dropdown-item:not(.cb-btn-trash)' );
		if ( dropdownItems.length > 0 ) {
			const firstItem = dropdownItems[ 0 ];
			firstItem.className = `cb-dropdown-item ${ config.dropdownClass }`;
			firstItem.dataset.status = config.dropdownStatus;
			firstItem.innerHTML = `<span class="dashicons ${ config.dropdownIcon }"></span>${ config.dropdownLabel }`;
		}

		// Update dropdown data-current-status
		dropdown.dataset.currentStatus = newStatus;
	}

	/**
	 * Check if we're in question edit context
	 * @return {boolean}
	 */
	isQuestionContext() {
		return !! document.querySelector( BuilderEditQuestion.selectors.elDataQuestion );
	}

	getQuestionDataForUpdate() {
		const data = {};

		const wrapperEl = document.querySelector( BuilderEditQuestion.selectors.elDataQuestion );

		data.question_id = wrapperEl ? parseInt( wrapperEl.dataset.questionId ) || 0 : 0;

		const titleInput = document.getElementById( BuilderEditQuestion.selectors.idTitle );
		data.question_title = titleInput ? titleInput.value : '';

		const descEditor = document.getElementById( BuilderEditQuestion.selectors.idDescEditor );
		data.question_description = descEditor ? descEditor.value : '';

		if ( typeof tinymce !== 'undefined' ) {
			const editor = tinymce.get( BuilderEditQuestion.selectors.idDescEditor );
			if ( editor ) {
				data.question_description = editor.getContent();
			}
		}

		const permalinkInput = document.querySelector(
			BuilderEditQuestion.selectors.elPermalinkSlugInput
		);
		if ( permalinkInput && permalinkInput.value ) {
			data.question_permalink = permalinkInput.value;
		}

		const elFormSetting = document.querySelector( BuilderEditQuestion.selectors.elFormSetting );

		if ( elFormSetting ) {
			data.question_settings = true;
			const formElements = elFormSetting.querySelectorAll( 'input, select, textarea' );

			formElements.forEach( ( element ) => {
				const name = element.name || element.id;

				if ( ! name ) {
					return;
				}

				if ( name === 'learnpress_meta_box_nonce' || name === '_wp_http_referer' ) {
					return;
				}

				if ( element.type === 'checkbox' ) {
					const fieldName = name.replace( '[]', '' );
					if ( ! data.hasOwnProperty( fieldName ) ) {
						data[ fieldName ] = element.checked ? 'yes' : 'no';
					}
				} else if ( element.type === 'radio' ) {
					if ( element.checked ) {
						const fieldName = name.replace( '[]', '' );
						data[ fieldName ] = element.value;
					}
				} else if ( element.type === 'file' ) {
					const fieldName = name.replace( '[]', '' );
					if ( element.files && element.files.length > 0 ) {
						data[ fieldName ] = element.files;
					}
				} else {
					const fieldName = name.replace( '[]', '' );

					if ( name.endsWith( '[]' ) ) {
						if ( ! data.hasOwnProperty( fieldName ) ) {
							data[ fieldName ] = [];
						}

						if ( Array.isArray( data[ fieldName ] ) ) {
							data[ fieldName ].push( element.value );
						}
					} else {
						if ( ! data.hasOwnProperty( fieldName ) ) {
							data[ fieldName ] = element.value;
						}
					}
				}
			} );

			Object.keys( data ).forEach( ( key ) => {
				if ( Array.isArray( data[ key ] ) ) {
					data[ key ] = data[ key ].join( ',' );
				}
			} );
		}

		return data;
	}

	getStatusFromPublishPanel( fallbackStatus = 'publish' ) {
		const statusSelect = document.querySelector(
			BuilderEditQuestion.selectors.elPublishStatusSelect
		);
		if ( ! statusSelect ) {
			return fallbackStatus;
		}

		const selectedStatus = statusSelect.value;
		if ( selectedStatus === 'publish' || selectedStatus === 'draft' ) {
			return selectedStatus;
		}

		return fallbackStatus;
	}

	hasPublishDrivenSingleAction() {
		if ( ! document.querySelector( BuilderEditQuestion.selectors.elPublishStatusSelect ) ) {
			return false;
		}

		const wrapperEl = document.querySelector( BuilderEditQuestion.selectors.elDataQuestion );
		const questionId = wrapperEl ? parseInt( wrapperEl.dataset.questionId ) || 0 : 0;

		return questionId > 0;
	}

	syncHeaderActionWithPublishPanel() {
		if ( ! this.hasPublishDrivenSingleAction() ) {
			return;
		}

		const dropdown = document.querySelector(
			BuilderEditQuestion.selectors.elHeaderActionsDropdown
		);
		if ( ! dropdown ) {
			return;
		}

		const mainBtn = dropdown.querySelector( '.cb-btn-main-action' );
		if ( ! mainBtn ) {
			return;
		}

		const toggleBtn = dropdown.querySelector( BuilderEditQuestion.selectors.elDropdownToggle );
		const dropdownMenu = dropdown.querySelector( BuilderEditQuestion.selectors.elDropdownMenu );
		const status = this.getStatusFromPublishPanel( mainBtn.dataset.status || 'publish' );

		mainBtn.classList.remove( 'cb-btn-darft', 'cb-btn-publish', 'cb-btn-pending' );
		mainBtn.classList.add( 'cb-btn-update', 'cb-btn-primary', 'cb-btn-main-action' );
		mainBtn.textContent = mainBtn.dataset.titleUpdate || 'Update';
		mainBtn.dataset.status = status;
		dropdown.dataset.currentStatus = status;
		dropdown.classList.add( 'cb-header-actions-dropdown--single' );

		if ( toggleBtn ) {
			toggleBtn.style.display = 'none';
		}
		if ( dropdownMenu ) {
			dropdownMenu.style.display = 'none';
		}
	}

	syncPublishPanelStatus( status ) {
		const statusSelect = document.querySelector(
			BuilderEditQuestion.selectors.elPublishStatusSelect
		);
		if ( ! statusSelect ) {
			return;
		}

		statusSelect.value = status === 'publish' ? 'publish' : 'draft';
		this.syncHeaderActionWithPublishPanel();
	}

	updatePermalinkUIAfterSave( data = {} ) {
		const slugInput = document.querySelector( BuilderEditQuestion.selectors.elPermalinkSlugInput );
		const urlLink = document.querySelector( BuilderEditQuestion.selectors.elPermalinkUrl );
		const baseUrlInput = document.querySelector( BuilderEditQuestion.selectors.elPermalinkBaseUrl );
		const permalinkDisplayUrl = this.buildPermalinkDisplayUrl(
			baseUrlInput ? baseUrlInput.value : '',
			data?.question_slug,
			data?.question_permalink
		);

		if ( slugInput && data?.question_slug ) {
			slugInput.value = data.question_slug;
			slugInput.dataset.originalValue = data.question_slug;
		}

		if ( urlLink && data?.question_permalink ) {
			urlLink.href = data.question_permalink;
			urlLink.textContent = permalinkDisplayUrl || data.question_permalink;
		} else if ( urlLink && permalinkDisplayUrl ) {
			urlLink.textContent = permalinkDisplayUrl;
		}
	}

	slugify( str ) {
		return ( str || '' )
			.toString()
			.normalize( 'NFD' )
			.replace( /[\u0300-\u036f]/g, '' )
			.replace( /đ/g, 'd' )
			.replace( /Đ/g, 'D' )
			.toLowerCase()
			.replace( /\s+/g, '-' )
			.replace( /[^\w-]+/g, '' )
			.replace( /--+/g, '-' )
			.replace( /^-+/, '' )
			.replace( /-+$/, '' );
	}

	buildPermalinkDisplayUrl( baseUrl = '', slug = '', fallbackUrl = '' ) {
		const normalizedBaseUrl = typeof baseUrl === 'string' ? baseUrl : '';
		const normalizedSlug = typeof slug === 'string' ? slug.trim() : '';

		if ( normalizedBaseUrl && normalizedSlug ) {
			return `${ normalizedBaseUrl }${ normalizedSlug }`;
		}

		return typeof fallbackUrl === 'string' ? fallbackUrl : '';
	}

	handlePermalinkEdit( args ) {
		if ( ! this.isQuestionContext() ) {
			return;
		}

		const { e } = args;
		if ( e ) e.preventDefault();

		const display = document.querySelector( BuilderEditQuestion.selectors.elPermalinkDisplay );
		const editor = document.querySelector( BuilderEditQuestion.selectors.elPermalinkEditor );
		const input = document.querySelector( BuilderEditQuestion.selectors.elPermalinkSlugInput );

		if ( ! display || ! editor || ! input ) return;

		input.dataset.originalValue = input.value;
		display.classList.add( 'lp-hidden' );
		editor.classList.remove( 'lp-hidden' );
		input.focus();
		input.select();
	}

	handlePermalinkOk( args ) {
		if ( ! this.isQuestionContext() ) {
			return;
		}

		const { e } = args;
		if ( e ) e.preventDefault();

		const display = document.querySelector( BuilderEditQuestion.selectors.elPermalinkDisplay );
		const editor = document.querySelector( BuilderEditQuestion.selectors.elPermalinkEditor );
		const input = document.querySelector( BuilderEditQuestion.selectors.elPermalinkSlugInput );
		const urlLink = document.querySelector( BuilderEditQuestion.selectors.elPermalinkUrl );
		const baseUrlInput = document.querySelector( BuilderEditQuestion.selectors.elPermalinkBaseUrl );

		if ( ! display || ! editor || ! input || ! urlLink ) return;

		let newSlug = this.slugify( input.value.trim() );
		if ( ! newSlug ) {
			newSlug = input.dataset.originalValue || 'question';
		}

		input.value = newSlug;
		const baseUrl = baseUrlInput ? baseUrlInput.value : '';
		const newUrl = this.buildPermalinkDisplayUrl( baseUrl, newSlug, urlLink.textContent || '' );

		// Keep href as the current saved link and only update display text.
		urlLink.textContent = newUrl;
		editor.classList.add( 'lp-hidden' );
		display.classList.remove( 'lp-hidden' );

		if ( newSlug !== input.dataset.originalValue ) {
			getFormState().markAsChanged();
		}
	}

	handlePermalinkCancel( args ) {
		if ( ! this.isQuestionContext() ) {
			return;
		}

		const { e } = args;
		if ( e ) e.preventDefault();

		const display = document.querySelector( BuilderEditQuestion.selectors.elPermalinkDisplay );
		const editor = document.querySelector( BuilderEditQuestion.selectors.elPermalinkEditor );
		const input = document.querySelector( BuilderEditQuestion.selectors.elPermalinkSlugInput );

		if ( ! display || ! editor || ! input ) return;

		input.value = input.dataset.originalValue || '';
		editor.classList.add( 'lp-hidden' );
		display.classList.remove( 'lp-hidden' );
	}

	async updateQuestion( args ) {
		// Context check: only handle if on question edit page
		if ( ! this.isQuestionContext() ) {
			return;
		}

		const { target } = args;
		const elBtnMainAction = target.closest( BuilderEditQuestion.selectors.elBtnMainAction );

		if ( ! elBtnMainAction ) {
			return;
		}

		// Validate title before update
		if ( ! this.validateTitleBeforeUpdate() ) {
			return;
		}

		// Resolve status from Overview publish panel first, fallback to button data-status.
		const targetStatus = this.getStatusFromPublishPanel(
			elBtnMainAction.dataset.status || 'publish'
		);
		const canContinue = await this.confirmUnpublishIfNeeded( targetStatus, elBtnMainAction );
		if ( ! canContinue ) {
			return;
		}

		lpUtils.lpSetLoadingEl( elBtnMainAction, 1 );

		const questionData = this.getQuestionDataForUpdate();

		const dataSend = {
			...questionData,
			action: 'builder_update_question',
			args: {
				id_url: 'builder-update-question',
			},
			question_status: targetStatus,
		};

		if ( typeof lpQuestionBuilder !== 'undefined' && lpQuestionBuilder.nonce ) {
			dataSend.nonce = lpQuestionBuilder.nonce;
		}

		const callBack = {
			success: ( response ) => {
				const { status, message, data } = response;
				lpToastify.show( message, status );

				if ( status === 'success' ) {
					// Update action button text with actual status from server
					this.updateActionButtons( data?.status || targetStatus );
					this.syncPublishPanelStatus( data?.status || targetStatus );
					this.updatePermalinkUIAfterSave( data );

					// Reset form state before potential redirect.
					document.dispatchEvent( new CustomEvent( 'lp-course-builder-saved' ) );

					if ( data?.redirect_url ) {
						window.location.href = data.redirect_url;
					}

					if ( data?.status ) {
						const elStatus = document.querySelector(
							BuilderEditQuestion.selectors.elQuestionStatus
						);
						if ( elStatus ) {
							elStatus.className = 'question-status ' + data.status;
							elStatus.textContent = data.status;
						}

						if ( data.status === 'trash' || data.status === 'draft' ) {
							const curriculumItem = document.querySelector(
								`.question-item[data-question-id="${ questionData.question_id }"]`
							);
							if ( curriculumItem ) {
								const elCurriculumQuestion = curriculumItem.closest( '.question' );
								if ( elCurriculumQuestion ) {
									elCurriculumQuestion.remove();
								}
							}
						}
					}
				}
			},
			error: ( error ) => {
				lpToastify.show( error.message || error, 'error' );
			},
			completed: () => {
				lpUtils.lpSetLoadingEl( elBtnMainAction, 0 );
			},
		};

		window.lpAJAXG.fetchAJAX( dataSend, callBack );
	}

	async confirmUnpublishIfNeeded( targetStatus, triggerEl ) {
		if ( targetStatus !== 'draft' ) {
			return true;
		}

		const statusEl = document.querySelector( BuilderEditQuestion.selectors.elQuestionStatus );
		const isPublished = statusEl && statusEl.classList.contains( 'publish' );
		if ( ! isPublished ) {
			return true;
		}

		const confirmMsg =
			triggerEl?.dataset?.confirmUnpublish ||
			'Saving as draft will unpublish this item. Are you sure?';
		const result = await SweetAlert.fire( {
			title: confirmMsg,
			iconHtml: SWAL_ICON_TRASH_DRAFT,
			customClass: { icon: 'lp-cb-swal-icon-html' },
			showCloseButton: true,
			showCancelButton: true,
			cancelButtonText: lpData.i18n.cancel,
			confirmButtonText: lpData.i18n.yes,
			reverseButtons: true,
		} );

		return !! result.isConfirmed;
	}

	async saveDraftQuestion( args ) {
		// Context check: only handle if on question edit page
		if ( ! this.isQuestionContext() ) {
			return;
		}

		const { target } = args;
		const elBtnDraftQuestion = target.closest( BuilderEditQuestion.selectors.elBtnDraftQuestion );

		if ( ! elBtnDraftQuestion ) {
			return;
		}

		// Check if published to show confirm unpublish modal
		const statusEl = document.querySelector( BuilderEditQuestion.selectors.elQuestionStatus );
		const isPublished = statusEl && statusEl.classList.contains( 'publish' );
		if ( isPublished ) {
			const confirmMsg =
				elBtnDraftQuestion.dataset.confirmUnpublish ||
				'Saving as draft will unpublish this item. Are you sure?';
			const result = await SweetAlert.fire( {
				title: confirmMsg,
				iconHtml: SWAL_ICON_TRASH_DRAFT,
				customClass: { icon: 'lp-cb-swal-icon-html' },
				showCloseButton: true,
				showCancelButton: true,
				cancelButtonText: lpData.i18n.cancel,
				confirmButtonText: lpData.i18n.yes,
				reverseButtons: true,
			} );

			if ( ! result.isConfirmed ) {
				return;
			}
		}

		// Validate title before saving draft
		if ( ! this.validateTitleBeforeUpdate() ) {
			return;
		}

		const elActionExpanded = document.querySelector(
			BuilderEditQuestion.selectors.elQuestionActionExpanded
		);

		lpUtils.lpSetLoadingEl( elActionExpanded, 1 );

		const questionData = this.getQuestionDataForUpdate();

		const dataSend = {
			...questionData,
			action: 'builder_update_question',
			args: {
				id_url: 'builder-update-question',
			},
			question_status: 'draft',
		};

		if ( typeof lpQuestionBuilder !== 'undefined' && lpQuestionBuilder.nonce ) {
			dataSend.nonce = lpQuestionBuilder.nonce;
		}

		const callBack = {
			success: ( response ) => {
				const { status, message, data } = response;
				lpToastify.show( message, status );

				if ( status === 'success' ) {
					// Update action button text
					this.updateActionButtons( 'draft' );
					this.syncPublishPanelStatus( data?.status || 'draft' );
					this.updatePermalinkUIAfterSave( data );

					// Reset form state before potential redirect.
					document.dispatchEvent( new CustomEvent( 'lp-course-builder-saved' ) );

					if ( data?.redirect_url ) {
						window.location.href = data.redirect_url;
					}

					if ( data?.status ) {
						const elStatus = document.querySelector(
							BuilderEditQuestion.selectors.elQuestionStatus
						);
						if ( elStatus ) {
							elStatus.className = 'question-status ' + data.status;
							elStatus.textContent = data.status;
						}

						if ( data.status === 'trash' || data.status === 'draft' ) {
							const curriculumItem = document.querySelector(
								`.question-item[data-question-id="${ questionData.question_id }"]`
							);
							if ( curriculumItem ) {
								const elCurriculumQuestion = curriculumItem.closest( '.question' );
								if ( elCurriculumQuestion ) {
									elCurriculumQuestion.remove();
								}
							}
						}
					}
				}
			},
			error: ( error ) => {
				lpToastify.show( error.message || error, 'error' );
			},
			completed: () => {
				lpUtils.lpSetLoadingEl( elActionExpanded, 0 );
			},
		};

		window.lpAJAXG.fetchAJAX( dataSend, callBack );
	}

	async trashQuestion( args ) {
		// Context check: only handle if on question edit page
		if ( ! this.isQuestionContext() ) {
			return;
		}

		const { target } = args;
		const elBtnTrashQuestion = target.closest( BuilderEditQuestion.selectors.elBtnTrashQuestion );
		if ( ! elBtnTrashQuestion ) {
			return;
		}

		const result = await SweetAlert.fire( {
			title: 'Are you sure you want to trash this question?',
			iconHtml: SWAL_ICON_TRASH_DRAFT,
			customClass: { icon: 'lp-cb-swal-icon-html' },
			showCloseButton: true,
			showCancelButton: true,
			cancelButtonText: lpData.i18n.cancel,
			confirmButtonText: lpData.i18n.yes,
			reverseButtons: true,
		} );

		if ( ! result.isConfirmed ) {
			return;
		}

		const elActionExpanded = document.querySelector(
			BuilderEditQuestion.selectors.elQuestionActionExpanded
		);
		lpUtils.lpSetLoadingEl( elActionExpanded, 1 );

		const questionData = this.getQuestionDataForUpdate();
		const dataSend = {
			action: 'move_trash_question',
			args: {
				id_url: 'move-trash-question',
			},
			question_id: questionData.question_id || 0,
		};

		const callBack = {
			success: ( response ) => {
				const { status, message, data } = response;
				lpToastify.show( message, status );

				if ( status === 'success' ) {
					if ( data?.redirect_url ) {
						window.location.href = data.redirect_url;
					}

					if ( data?.status ) {
						const elStatus = document.querySelector(
							BuilderEditQuestion.selectors.elQuestionStatus
						);
						if ( elStatus ) {
							elStatus.className = 'question-status ' + data.status;
							elStatus.textContent = data.status;
						}
						if ( data.status === 'trash' || data.status === 'draft' ) {
							const curriculumItem = document.querySelector(
								`.question-item[data-question-id="${ questionData.question_id }"]`
							);
							if ( curriculumItem ) {
								const elCurriculumQuestion = curriculumItem.closest( '.question' );
								if ( elCurriculumQuestion ) {
									elCurriculumQuestion.remove();
								}
							}
						}
					}
				}
			},
			error: ( error ) => {
				lpToastify.show( error.message || error, 'error' );
			},
			completed: () => {
				lpUtils.lpSetLoadingEl( elActionExpanded, 0 );
			},
		};

		window.lpAJAXG.fetchAJAX( dataSend, callBack );
	}

	async duplicateQuestion( args ) {
		// Context check: only handle if on question edit page
		if ( ! this.isQuestionContext() ) {
			return;
		}

		const { target } = args;
		const elBtnDuplicateQuestion = target.closest(
			BuilderEditQuestion.selectors.elBtnDuplicateQuestion
		);
		if ( ! elBtnDuplicateQuestion ) {
			return;
		}

		const questionData = this.getQuestionDataForUpdate();
		const questionId = parseInt( questionData?.question_id, 10 ) || 0;
		if ( ! questionId ) {
			return;
		}

		const result = await SweetAlert.fire( {
			title: elBtnDuplicateQuestion.dataset.title || 'Are you sure?',
			text:
				elBtnDuplicateQuestion.dataset.content ||
				'Are you sure you want to duplicate this question?',
			iconHtml: SWAL_ICON_DUPLICATE,
			customClass: { icon: 'lp-cb-swal-icon-html' },
			showCloseButton: true,
			showCancelButton: true,
			cancelButtonText: lpData.i18n.cancel,
			confirmButtonText: lpData.i18n.yes,
			reverseButtons: true,
		} );

		if ( ! result.isConfirmed ) {
			return;
		}

		const elActionExpanded = document.querySelector(
			BuilderEditQuestion.selectors.elQuestionActionExpanded
		);

		lpUtils.lpSetLoadingEl( elActionExpanded, 1 );

		const dataSend = {
			action: 'duplicate_question',
			args: {
				id_url: 'duplicate-question',
			},
			question_id: questionId,
		};

		const callBack = {
			success: ( response ) => {
				const { status, message, data } = response;
				lpToastify.show( message || 'Duplicated successfully!', status );

				if ( status === 'success' && data?.redirect_url ) {
					window.location.href = data.redirect_url;
				}
			},
			error: ( error ) => {
				lpToastify.show( error.message || error, 'error' );
			},
			completed: () => {
				lpUtils.lpSetLoadingEl( elActionExpanded, 0 );
			},
		};

		window.lpAJAXG.fetchAJAX( dataSend, callBack );
	}
}

```
