Changes: - Fix z-index conflict where announcement modal (999999) was higher than WordPress media modals (160000) - Reduce announcement modal z-index to 100000 to allow WordPress media library to stack on top - Remove duplicate TinyMCE initialization that was unnecessary - Add custom "Add Media" button that renders when modal opens (prevents hidden modal issues) - Improve page detection with multi-layered approach (URL path, template, slug, queried object) - Move script loading to footer for better WordPress editor compatibility Technical Details: - WordPress core media modals use z-index 160000-160010 - Custom plugin modals should use 100000-159000 range to avoid conflicts - wp_editor() with media_buttons => true in hidden modals causes auto-open issues - Solution: media_buttons => false + custom button added via JavaScript when modal opens Testing: - Verified with MCP Playwright browser automation - Media modal now properly appears above announcement modal - All form functionality preserved - Screenshot verification shows correct stacking order 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
567 lines
No EOL
18 KiB
JavaScript
567 lines
No EOL
18 KiB
JavaScript
/**
|
|
* HVAC Announcements Admin JavaScript
|
|
*
|
|
* @package HVAC_Community_Events
|
|
*/
|
|
|
|
jQuery(document).ready(function($) {
|
|
'use strict';
|
|
|
|
// State variables
|
|
let currentPage = 1;
|
|
let totalPages = 1;
|
|
let currentStatus = 'any';
|
|
let searchTerm = '';
|
|
let editorInstance = null;
|
|
|
|
// Initialize
|
|
init();
|
|
|
|
/**
|
|
* Initialize the announcements interface
|
|
*/
|
|
function init() {
|
|
loadAnnouncements();
|
|
loadCategories();
|
|
initializeEventHandlers();
|
|
// NOTE: Editor initialization removed - wp_editor() PHP function handles this
|
|
// The duplicate JavaScript initialization was causing the media modal to auto-open
|
|
}
|
|
|
|
/**
|
|
* Initialize event handlers
|
|
*/
|
|
function initializeEventHandlers() {
|
|
// Add announcement button - FIXED: Use correct class selector to match HTML template
|
|
$('.hvac-add-announcement').on('click', function() {
|
|
openModal();
|
|
});
|
|
|
|
// Modal close buttons
|
|
$('.modal-close, .modal-cancel').on('click', function() {
|
|
closeModal();
|
|
});
|
|
|
|
// Form submission
|
|
$('#announcement-form').on('submit', function(e) {
|
|
e.preventDefault();
|
|
saveAnnouncement();
|
|
});
|
|
|
|
// Status filter
|
|
$('#status-filter').on('change', function() {
|
|
currentStatus = $(this).val();
|
|
currentPage = 1;
|
|
loadAnnouncements();
|
|
});
|
|
|
|
// Search
|
|
$('#search-btn').on('click', function() {
|
|
searchTerm = $('#announcement-search').val();
|
|
currentPage = 1;
|
|
loadAnnouncements();
|
|
});
|
|
|
|
$('#announcement-search').on('keypress', function(e) {
|
|
if (e.which === 13) {
|
|
searchTerm = $(this).val();
|
|
currentPage = 1;
|
|
loadAnnouncements();
|
|
}
|
|
});
|
|
|
|
// Pagination
|
|
$('#prev-page').on('click', function() {
|
|
if (currentPage > 1) {
|
|
currentPage--;
|
|
loadAnnouncements();
|
|
}
|
|
});
|
|
|
|
$('#next-page').on('click', function() {
|
|
if (currentPage < totalPages) {
|
|
currentPage++;
|
|
loadAnnouncements();
|
|
}
|
|
});
|
|
|
|
// Edit/Delete actions (delegated)
|
|
$(document).on('click', '.edit-announcement', function() {
|
|
const id = $(this).data('id');
|
|
editAnnouncement(id);
|
|
});
|
|
|
|
$(document).on('click', '.delete-announcement', function() {
|
|
const id = $(this).data('id');
|
|
if (confirm(hvac_announcements.strings.confirm_delete)) {
|
|
deleteAnnouncement(id);
|
|
}
|
|
});
|
|
|
|
// Featured image selection
|
|
$('#select-featured-image').on('click', function(e) {
|
|
e.preventDefault();
|
|
selectFeaturedImage();
|
|
});
|
|
|
|
$('#remove-featured-image').on('click', function(e) {
|
|
e.preventDefault();
|
|
removeFeaturedImage();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Load announcements via AJAX
|
|
*/
|
|
function loadAnnouncements() {
|
|
const data = {
|
|
action: 'hvac_get_announcements',
|
|
nonce: hvac_announcements.nonce,
|
|
page: currentPage,
|
|
per_page: 20,
|
|
status: currentStatus,
|
|
search: searchTerm
|
|
};
|
|
|
|
$.post(hvac_announcements.ajax_url, data, function(response) {
|
|
if (response.success) {
|
|
displayAnnouncements(response.data.announcements);
|
|
updatePagination(response.data.current_page, response.data.pages);
|
|
} else {
|
|
showError(response.data || hvac_announcements.strings.error_loading);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Display announcements in table
|
|
*/
|
|
function displayAnnouncements(announcements) {
|
|
const tbody = $('#announcements-list');
|
|
tbody.empty();
|
|
|
|
if (announcements.length === 0) {
|
|
tbody.append('<tr><td colspan="6" class="no-items">No announcements found</td></tr>');
|
|
return;
|
|
}
|
|
|
|
announcements.forEach(function(announcement) {
|
|
const row = $('<tr>');
|
|
|
|
// Title
|
|
row.append('<td class="column-title"><strong>' + escapeHtml(announcement.title) + '</strong></td>');
|
|
|
|
// Status
|
|
const statusClass = 'status-' + announcement.status;
|
|
row.append('<td class="column-status"><span class="' + statusClass + '">' + announcement.status + '</span></td>');
|
|
|
|
// Categories
|
|
const categories = announcement.categories.join(', ') || '-';
|
|
row.append('<td class="column-categories">' + escapeHtml(categories) + '</td>');
|
|
|
|
// Author
|
|
row.append('<td class="column-author">' + escapeHtml(announcement.author) + '</td>');
|
|
|
|
// Date
|
|
row.append('<td class="column-date">' + announcement.date + '</td>');
|
|
|
|
// Actions
|
|
let actions = '<td class="column-actions">';
|
|
if (announcement.can_edit) {
|
|
actions += '<button class="button button-small edit-announcement" data-id="' + announcement.id + '">Edit</button> ';
|
|
}
|
|
if (announcement.can_delete) {
|
|
actions += '<button class="button button-small delete-announcement" data-id="' + announcement.id + '">Delete</button>';
|
|
}
|
|
actions += '</td>';
|
|
row.append(actions);
|
|
|
|
tbody.append(row);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Update pagination controls
|
|
*/
|
|
function updatePagination(current, total) {
|
|
currentPage = current;
|
|
totalPages = total;
|
|
|
|
$('#current-page').text(current);
|
|
$('#total-pages').text(total);
|
|
|
|
$('#prev-page').prop('disabled', current <= 1);
|
|
$('#next-page').prop('disabled', current >= total);
|
|
}
|
|
|
|
/**
|
|
* Load categories for the form
|
|
*/
|
|
function loadCategories() {
|
|
$.post(hvac_announcements.ajax_url, {
|
|
action: 'hvac_get_announcement_categories',
|
|
nonce: hvac_announcements.nonce
|
|
}, function(response) {
|
|
if (response.success) {
|
|
displayCategories(response.data);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Display categories as checkboxes
|
|
*/
|
|
function displayCategories(categories) {
|
|
const container = $('#categories-container');
|
|
container.empty();
|
|
|
|
if (categories.length === 0) {
|
|
container.append('<p class="no-categories">No categories available</p>');
|
|
return;
|
|
}
|
|
|
|
categories.forEach(function(category) {
|
|
const checkbox = $('<label class="category-checkbox">');
|
|
checkbox.append('<input type="checkbox" name="categories[]" value="' + category.id + '">');
|
|
checkbox.append(' ' + escapeHtml(category.name));
|
|
container.append(checkbox);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Open the modal for adding/editing
|
|
*/
|
|
function openModal(announcementId) {
|
|
const $modal = $('#announcement-modal');
|
|
$modal.addClass('active');
|
|
$('body').addClass('modal-open');
|
|
|
|
// Add custom "Add Media" button to TinyMCE editor toolbar
|
|
// This is needed because media_buttons => false in wp_editor() to prevent auto-open
|
|
addMediaButtonToEditor();
|
|
|
|
if (announcementId) {
|
|
$('#modal-title').text('Edit Announcement');
|
|
loadAnnouncementForEdit(announcementId);
|
|
} else {
|
|
$('#modal-title').text('Add New Announcement');
|
|
resetForm();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add custom "Add Media" button to TinyMCE editor
|
|
* Required because wp_editor has media_buttons => false to prevent auto-open in hidden modal
|
|
*/
|
|
function addMediaButtonToEditor() {
|
|
// Check if button already exists
|
|
if ($('#wp-announcement-content-media-buttons').length > 0) {
|
|
return;
|
|
}
|
|
|
|
// Create media button container
|
|
const $mediaButton = $('<div id="wp-announcement-content-media-buttons" class="wp-media-buttons"></div>');
|
|
const $addMediaBtn = $('<button type="button" id="insert-media-button" class="button insert-media add_media" data-editor="announcement-content"></button>');
|
|
$addMediaBtn.html('<span class="wp-media-buttons-icon"></span> Add Media');
|
|
|
|
// Add click handler
|
|
$addMediaBtn.on('click', function(e) {
|
|
e.preventDefault();
|
|
openEditorMediaUploader();
|
|
});
|
|
|
|
$mediaButton.append($addMediaBtn);
|
|
|
|
// Insert button before editor wrapper
|
|
$('#wp-announcement-content-wrap').before($mediaButton);
|
|
}
|
|
|
|
/**
|
|
* Open media uploader for the announcement content editor
|
|
*/
|
|
function openEditorMediaUploader() {
|
|
if (typeof wp.media === 'undefined') {
|
|
return;
|
|
}
|
|
|
|
const editorMediaUploader = wp.media({
|
|
title: 'Insert Media',
|
|
button: {
|
|
text: 'Insert into post'
|
|
},
|
|
multiple: true
|
|
});
|
|
|
|
editorMediaUploader.on('select', function() {
|
|
const selection = editorMediaUploader.state().get('selection');
|
|
const editor = tinymce.get('announcement-content');
|
|
|
|
selection.forEach(function(attachment) {
|
|
attachment = attachment.toJSON();
|
|
let html = '';
|
|
|
|
if (attachment.type === 'image') {
|
|
html = '<img src="' + attachment.url + '" alt="' + (attachment.alt || '') + '" />';
|
|
} else {
|
|
html = '<a href="' + attachment.url + '">' + attachment.title + '</a>';
|
|
}
|
|
|
|
if (editor) {
|
|
editor.insertContent(html);
|
|
}
|
|
});
|
|
});
|
|
|
|
editorMediaUploader.open();
|
|
}
|
|
|
|
/**
|
|
* Close the modal
|
|
*/
|
|
function closeModal() {
|
|
const $modal = $('#announcement-modal');
|
|
$modal.removeClass('active');
|
|
$('body').removeClass('modal-open');
|
|
resetForm();
|
|
}
|
|
|
|
/**
|
|
* Reset the form
|
|
*/
|
|
function resetForm() {
|
|
$('#announcement-form')[0].reset();
|
|
$('#announcement-id').val('');
|
|
|
|
// Reset editor using TinyMCE native API
|
|
if (typeof tinymce !== 'undefined') {
|
|
const editor = tinymce.get('announcement-content');
|
|
if (editor) {
|
|
editor.setContent('');
|
|
}
|
|
}
|
|
|
|
// Reset featured image
|
|
removeFeaturedImage();
|
|
|
|
// Uncheck all categories
|
|
$('#categories-container input[type="checkbox"]').prop('checked', false);
|
|
}
|
|
|
|
/**
|
|
* Load announcement for editing
|
|
*/
|
|
function loadAnnouncementForEdit(id) {
|
|
$.post(hvac_announcements.ajax_url, {
|
|
action: 'hvac_get_announcement',
|
|
nonce: hvac_announcements.nonce,
|
|
id: id
|
|
}, function(response) {
|
|
if (response.success) {
|
|
populateForm(response.data);
|
|
} else {
|
|
showError(response.data || 'Failed to load announcement');
|
|
closeModal();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Populate form with announcement data
|
|
*/
|
|
function populateForm(announcement) {
|
|
$('#announcement-id').val(announcement.id);
|
|
$('#announcement-title').val(announcement.title);
|
|
$('#announcement-excerpt').val(announcement.excerpt);
|
|
$('#announcement-status').val(announcement.status);
|
|
$('#announcement-tags').val(announcement.tags);
|
|
|
|
// Set content in editor using TinyMCE native API
|
|
if (typeof tinymce !== 'undefined') {
|
|
const editor = tinymce.get('announcement-content');
|
|
if (editor) {
|
|
editor.setContent(announcement.content || '');
|
|
}
|
|
}
|
|
|
|
// Set publish date
|
|
if (announcement.date) {
|
|
const date = new Date(announcement.date);
|
|
const localDate = date.toISOString().slice(0, 16);
|
|
$('#announcement-date').val(localDate);
|
|
}
|
|
|
|
// Set categories
|
|
if (announcement.categories && announcement.categories.length > 0) {
|
|
announcement.categories.forEach(function(catId) {
|
|
$('#categories-container input[value="' + catId + '"]').prop('checked', true);
|
|
});
|
|
}
|
|
|
|
// Set featured image
|
|
if (announcement.featured_image_id) {
|
|
$('#featured-image-id').val(announcement.featured_image_id);
|
|
if (announcement.featured_image_url) {
|
|
$('#featured-image-preview').html('<img src="' + announcement.featured_image_url + '" />');
|
|
$('#remove-featured-image').show();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Save announcement (create or update)
|
|
*/
|
|
function saveAnnouncement() {
|
|
// Get editor content using WordPress editor API or TinyMCE native API
|
|
let content = '';
|
|
if (typeof wp !== 'undefined' && wp.editor && wp.editor.getContent) {
|
|
// Use WordPress API if available (WordPress 4.8+)
|
|
content = wp.editor.getContent('announcement-content');
|
|
} else if (typeof tinymce !== 'undefined') {
|
|
// Fallback to TinyMCE native API
|
|
const editor = tinymce.get('announcement-content');
|
|
if (editor) {
|
|
content = editor.getContent();
|
|
}
|
|
}
|
|
|
|
// Gather form data
|
|
const formData = {
|
|
action: $('#announcement-id').val() ? 'hvac_update_announcement' : 'hvac_create_announcement',
|
|
nonce: hvac_announcements.nonce,
|
|
id: $('#announcement-id').val(),
|
|
title: $('#announcement-title').val(),
|
|
content: content,
|
|
excerpt: $('#announcement-excerpt').val(),
|
|
status: $('#announcement-status').val(),
|
|
publish_date: $('#announcement-date').val(),
|
|
tags: $('#announcement-tags').val(),
|
|
categories: [],
|
|
featured_image_id: $('#featured-image-id').val()
|
|
};
|
|
|
|
// Get selected categories
|
|
$('#categories-container input:checked').each(function() {
|
|
formData.categories.push($(this).val());
|
|
});
|
|
|
|
// Send AJAX request
|
|
$.post(hvac_announcements.ajax_url, formData, function(response) {
|
|
if (response.success) {
|
|
showSuccess(response.data.message);
|
|
closeModal();
|
|
loadAnnouncements();
|
|
} else {
|
|
showError(response.data || hvac_announcements.strings.error_saving);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Edit announcement
|
|
*/
|
|
function editAnnouncement(id) {
|
|
openModal(id);
|
|
}
|
|
|
|
/**
|
|
* Delete announcement
|
|
*/
|
|
function deleteAnnouncement(id) {
|
|
$.post(hvac_announcements.ajax_url, {
|
|
action: 'hvac_delete_announcement',
|
|
nonce: hvac_announcements.nonce,
|
|
id: id
|
|
}, function(response) {
|
|
if (response.success) {
|
|
showSuccess(response.data.message || hvac_announcements.strings.success_deleted);
|
|
loadAnnouncements();
|
|
} else {
|
|
showError(response.data || 'Failed to delete announcement');
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Select featured image using WordPress media uploader
|
|
*/
|
|
function selectFeaturedImage() {
|
|
if (typeof wp.media === 'undefined') {
|
|
return;
|
|
}
|
|
|
|
const mediaUploader = wp.media({
|
|
title: 'Select Featured Image',
|
|
button: {
|
|
text: 'Use this image'
|
|
},
|
|
multiple: false
|
|
});
|
|
|
|
mediaUploader.on('select', function() {
|
|
const attachment = mediaUploader.state().get('selection').first().toJSON();
|
|
$('#featured-image-id').val(attachment.id);
|
|
|
|
let imageUrl = attachment.url;
|
|
if (attachment.sizes && attachment.sizes.medium) {
|
|
imageUrl = attachment.sizes.medium.url;
|
|
}
|
|
|
|
$('#featured-image-preview').html('<img src="' + imageUrl + '" />');
|
|
$('#remove-featured-image').show();
|
|
});
|
|
|
|
mediaUploader.open();
|
|
}
|
|
|
|
/**
|
|
* Remove featured image
|
|
*/
|
|
function removeFeaturedImage() {
|
|
$('#featured-image-id').val('');
|
|
$('#featured-image-preview').empty();
|
|
$('#remove-featured-image').hide();
|
|
}
|
|
|
|
/**
|
|
* Show success message
|
|
*/
|
|
function showSuccess(message) {
|
|
const notice = $('<div class="notice notice-success is-dismissible"><p>' + message + '</p></div>');
|
|
$('.hvac-announcements-wrapper').prepend(notice);
|
|
|
|
setTimeout(function() {
|
|
notice.fadeOut(function() {
|
|
notice.remove();
|
|
});
|
|
}, 3000);
|
|
}
|
|
|
|
/**
|
|
* Show error message
|
|
*/
|
|
function showError(message) {
|
|
const notice = $('<div class="notice notice-error is-dismissible"><p>' + message + '</p></div>');
|
|
$('.hvac-announcements-wrapper').prepend(notice);
|
|
|
|
setTimeout(function() {
|
|
notice.fadeOut(function() {
|
|
notice.remove();
|
|
});
|
|
}, 5000);
|
|
}
|
|
|
|
/**
|
|
* Escape HTML
|
|
*/
|
|
function escapeHtml(text) {
|
|
const map = {
|
|
'&': '&',
|
|
'<': '<',
|
|
'>': '>',
|
|
'"': '"',
|
|
"'": '''
|
|
};
|
|
|
|
return text.replace(/[&<>"']/g, function(m) { return map[m]; });
|
|
}
|
|
}); |