feat: initial ACRIB WordPress deployment
- WordPress 6.9.4 (es_ES) with Kadence theme - Homepage: Hero, La Asociación, Pilares, Beneficios, Eventos, Miembros, Hazte Miembro, Contacto - Brand identity: #13294b navy, #a12932 burgundy, #c69c48 gold - Fonts: Raleway (headings) + Source Sans 3 (body) + Lato (UI) - Plugins: Kadence Blocks, Polylang, Contact Form 7 - Custom CSS with full brand styling and responsive layout - HTTPS enforced via wp-config.php proxy detection
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
/* Kadence Optimizer Post List Table Column styles */
|
||||
.fixed {
|
||||
.column-kadence_optimizer {
|
||||
width: 11em;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { animateDots } from '../optimizer/ui-manager.js';
|
||||
|
||||
/**
|
||||
* Manages animation intervals for bulk operations.
|
||||
*/
|
||||
export class AnimationManager {
|
||||
constructor() {
|
||||
this.intervals = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start animation for a post link.
|
||||
*
|
||||
* @param {number} postId - Post ID.
|
||||
* @param {HTMLElement} link - Link element.
|
||||
*/
|
||||
start(postId, link) {
|
||||
const interval = animateDots(link);
|
||||
this.intervals.set(postId, interval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop animation for a post.
|
||||
*
|
||||
* @param {number} postId - Post ID.
|
||||
*/
|
||||
stop(postId) {
|
||||
const interval = this.intervals.get(postId);
|
||||
|
||||
if (interval) {
|
||||
clearInterval(interval);
|
||||
this.intervals.delete(postId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop all animations.
|
||||
*/
|
||||
stopAll() {
|
||||
this.intervals.forEach((interval) => clearInterval(interval));
|
||||
this.intervals.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
import { POSTS_METADATA_ROUTE, BULK_DELETE_ROUTE } from './constants.js';
|
||||
|
||||
/**
|
||||
* Fetch metadata for multiple posts.
|
||||
*
|
||||
* @param {number[]} postIds - Array of post IDs.
|
||||
*
|
||||
* @returns {Promise<{data: Array, errors: Array}>}
|
||||
*/
|
||||
export async function fetchPostsMetadata(postIds) {
|
||||
return await apiFetch({
|
||||
path: POSTS_METADATA_ROUTE,
|
||||
method: 'POST',
|
||||
data: { post_ids: postIds },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk delete optimization data for multiple posts.
|
||||
*
|
||||
* @param {number[]} postIds - Array of post IDs.
|
||||
* @param {string[]} postPaths - Array of post paths.
|
||||
*
|
||||
* @returns {Promise<{data: {successful: Array, failed: Array}}>}
|
||||
*/
|
||||
export async function bulkDeleteOptimization(postIds, postPaths) {
|
||||
return await apiFetch({
|
||||
path: BULK_DELETE_ROUTE,
|
||||
method: 'DELETE',
|
||||
data: {
|
||||
post_ids: postIds,
|
||||
post_paths: postPaths,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export const POSTS_METADATA_ROUTE = '/kb-optimizer/v1/optimize/posts-metadata';
|
||||
export const BULK_DELETE_ROUTE = '/kb-optimizer/v1/optimize/bulk/delete';
|
||||
@@ -0,0 +1,126 @@
|
||||
import { __, _n, sprintf } from '@wordpress/i18n';
|
||||
import { analyzeSite } from '../../optimizer/analyzer.js';
|
||||
import { OPTIMIZER_DATA, UI_STATES } from '../../optimizer/constants.js';
|
||||
import { createNotice, createDismissibleNotice, NOTICE_TYPES } from '@kadence-bundled/admin-notices';
|
||||
import { AnimationManager } from '../animation-manager.js';
|
||||
import { updatePostLinkState } from '../link-manager.js';
|
||||
import { fetchPostsMetadata } from '../api.js';
|
||||
import { initializeBulkUI } from '../ui.js';
|
||||
|
||||
/**
|
||||
* Handle errors from metadata fetch.
|
||||
*
|
||||
* @param {Array} errors - Array of error objects.
|
||||
* @param {AnimationManager} animationManager - Animation manager instance.
|
||||
*/
|
||||
function handleMetadataErrors(errors, animationManager) {
|
||||
if (!errors || errors.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
errors.forEach((error) => {
|
||||
createNotice(`Post ID ${error.post_id}: ${error.message}`, NOTICE_TYPES.ERROR, true);
|
||||
animationManager.stop(error.post_id);
|
||||
updatePostLinkState(error.post_id, UI_STATES.OPTIMIZE);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle bulk optimization action.
|
||||
*
|
||||
* @param {number[]} postIds - Array of post IDs to optimize.
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function handleBulkOptimize(postIds) {
|
||||
console.log('🚀 Starting bulk optimization for posts:', postIds);
|
||||
|
||||
const animationManager = new AnimationManager();
|
||||
initializeBulkUI(postIds, animationManager);
|
||||
|
||||
const processingNotice = createDismissibleNotice(
|
||||
sprintf(
|
||||
// translators: %d: Number of posts being optimized.
|
||||
_n('Optimizing %d post…', 'Optimizing %d posts…', postIds.length, 'kadence-blocks'),
|
||||
postIds.length
|
||||
),
|
||||
NOTICE_TYPES.INFO
|
||||
);
|
||||
|
||||
try {
|
||||
const response = await fetchPostsMetadata(postIds);
|
||||
const { data: postsData, errors } = response;
|
||||
|
||||
handleMetadataErrors(errors, animationManager);
|
||||
|
||||
const results = {
|
||||
successful: [],
|
||||
failed: [],
|
||||
};
|
||||
|
||||
for (const postData of postsData) {
|
||||
console.log(`🎯 Optimizing post ${postData.post_id}...`);
|
||||
|
||||
try {
|
||||
await analyzeSite(postData.url, postData.post_id, postData.post_path, OPTIMIZER_DATA.token);
|
||||
|
||||
results.successful.push(postData.post_id);
|
||||
animationManager.stop(postData.post_id);
|
||||
updatePostLinkState(postData.post_id, UI_STATES.REMOVE);
|
||||
} catch (error) {
|
||||
console.error(`❌ Failed to optimize post ${postData.post_id}:`, error);
|
||||
results.failed.push(postData.post_id);
|
||||
|
||||
createNotice(`Post ID ${postData.post_id}: ${error.message}`, NOTICE_TYPES.ERROR, true);
|
||||
animationManager.stop(postData.post_id);
|
||||
updatePostLinkState(postData.post_id, UI_STATES.OPTIMIZE);
|
||||
}
|
||||
}
|
||||
|
||||
if (results.successful.length > 0) {
|
||||
createNotice(
|
||||
sprintf(
|
||||
// translators: %d: Number of posts optimized.
|
||||
_n(
|
||||
'🎉 %d post optimized successfully!',
|
||||
'🎉 %d posts optimized successfully!',
|
||||
results.successful.length,
|
||||
'kadence-blocks'
|
||||
),
|
||||
results.successful.length
|
||||
),
|
||||
NOTICE_TYPES.SUCCESS
|
||||
);
|
||||
}
|
||||
|
||||
if (results.failed.length > 0) {
|
||||
createNotice(
|
||||
sprintf(
|
||||
// translators: %d: Number of posts that failed.
|
||||
_n(
|
||||
'Failed to optimize %d post.',
|
||||
'Failed to optimize %d posts.',
|
||||
results.failed.length,
|
||||
'kadence-blocks'
|
||||
),
|
||||
results.failed.length
|
||||
),
|
||||
NOTICE_TYPES.ERROR,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
processingNotice?.remove();
|
||||
} catch (error) {
|
||||
console.error('❌ Bulk optimization failed:', error);
|
||||
|
||||
processingNotice?.remove();
|
||||
|
||||
createNotice(
|
||||
__('Failed to fetch post data for bulk optimization.', 'kadence-blocks'),
|
||||
NOTICE_TYPES.ERROR,
|
||||
true,
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
import { __, _n, sprintf } from '@wordpress/i18n';
|
||||
import { UI_STATES } from '../../optimizer/constants.js';
|
||||
import { createNotice, createDismissibleNotice, NOTICE_TYPES } from '@kadence-bundled/admin-notices';
|
||||
import { AnimationManager } from '../animation-manager.js';
|
||||
import { updatePostLinkState } from '../link-manager.js';
|
||||
import { fetchPostsMetadata, bulkDeleteOptimization } from '../api.js';
|
||||
import { initializeBulkUI } from '../ui.js';
|
||||
|
||||
/**
|
||||
* Handle bulk remove optimization action.
|
||||
*
|
||||
* @param {number[]} postIds - Array of post IDs to remove optimization from.
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function handleBulkRemoveOptimization(postIds) {
|
||||
console.log('🗑️ Removing optimization for posts:', postIds);
|
||||
|
||||
const animationManager = new AnimationManager();
|
||||
initializeBulkUI(postIds, animationManager);
|
||||
|
||||
const processingNotice = createDismissibleNotice(
|
||||
sprintf(
|
||||
// translators: %d: Number of posts having optimization removed.
|
||||
_n(
|
||||
'Removing optimization for %d post…',
|
||||
'Removing optimization for %d posts…',
|
||||
postIds.length,
|
||||
'kadence-blocks'
|
||||
),
|
||||
postIds.length
|
||||
),
|
||||
NOTICE_TYPES.INFO
|
||||
);
|
||||
|
||||
try {
|
||||
const metadataResponse = await fetchPostsMetadata(postIds);
|
||||
const { data: postsData, errors: metadataErrors } = metadataResponse;
|
||||
|
||||
if (metadataErrors && metadataErrors.length > 0) {
|
||||
metadataErrors.forEach((error) => {
|
||||
createNotice(`Post ID ${error.post_id}: ${error.message}`, NOTICE_TYPES.ERROR, true);
|
||||
animationManager.stop(error.post_id);
|
||||
updatePostLinkState(error.post_id, UI_STATES.REMOVE);
|
||||
});
|
||||
}
|
||||
|
||||
if (postsData.length > 0) {
|
||||
const postIdsToDelete = postsData.map((post) => post.post_id);
|
||||
const postPathsToDelete = postsData.map((post) => post.post_path);
|
||||
|
||||
console.log(`Removing optimization for ${postIdsToDelete.length} posts...`);
|
||||
|
||||
const deleteResponse = await bulkDeleteOptimization(postIdsToDelete, postPathsToDelete);
|
||||
const { data: results } = deleteResponse;
|
||||
|
||||
results.successful.forEach((postId) => {
|
||||
animationManager.stop(postId);
|
||||
updatePostLinkState(postId, UI_STATES.OPTIMIZE);
|
||||
});
|
||||
|
||||
if (results.failed && results.failed.length > 0) {
|
||||
results.failed.forEach((failure) => {
|
||||
createNotice(`Post ID ${failure.post_id}: ${failure.message}`, NOTICE_TYPES.ERROR, true);
|
||||
animationManager.stop(failure.post_id);
|
||||
updatePostLinkState(failure.post_id, UI_STATES.REMOVE);
|
||||
});
|
||||
}
|
||||
|
||||
if (results.successful.length > 0) {
|
||||
createNotice(
|
||||
sprintf(
|
||||
// translators: %d: Number of posts.
|
||||
_n(
|
||||
'Removed optimization for %d post.',
|
||||
'Removed optimization for %d posts.',
|
||||
results.successful.length,
|
||||
'kadence-blocks'
|
||||
),
|
||||
results.successful.length
|
||||
),
|
||||
NOTICE_TYPES.SUCCESS
|
||||
);
|
||||
}
|
||||
|
||||
if (results.failed && results.failed.length > 0) {
|
||||
createNotice(
|
||||
sprintf(
|
||||
// translators: %d: Number of posts that failed.
|
||||
_n(
|
||||
'Failed to remove optimization for %d post.',
|
||||
'Failed to remove optimization for %d posts.',
|
||||
results.failed.length,
|
||||
'kadence-blocks'
|
||||
),
|
||||
results.failed.length
|
||||
),
|
||||
NOTICE_TYPES.ERROR,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
processingNotice?.remove();
|
||||
} catch (error) {
|
||||
console.error('❌ Bulk remove optimization failed:', error);
|
||||
|
||||
processingNotice?.remove();
|
||||
|
||||
createNotice(
|
||||
__('Failed to fetch post data for bulk operation.', 'kadence-blocks'),
|
||||
NOTICE_TYPES.ERROR,
|
||||
true,
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { handleBulkOptimize } from './handlers/optimize.js';
|
||||
import { handleBulkRemoveOptimization } from './handlers/remove-optimization.js';
|
||||
import { getSelectedPostIds } from './link-manager.js';
|
||||
|
||||
/**
|
||||
* Initialize bulk action handlers.
|
||||
*/
|
||||
export function initBulkActions() {
|
||||
const bulkActionForm = document.querySelector('#posts-filter');
|
||||
|
||||
if (!bulkActionForm) {
|
||||
return;
|
||||
}
|
||||
|
||||
bulkActionForm.addEventListener('submit', async (event) => {
|
||||
// Only handle bulk actions when the Apply button (doaction) is clicked.
|
||||
if (!event.submitter || event.submitter.id !== 'doaction') {
|
||||
return;
|
||||
}
|
||||
|
||||
const actionSelect = document.querySelector('#bulk-action-selector-top');
|
||||
const action = actionSelect ? actionSelect.value : null;
|
||||
|
||||
if (action === 'kb_optimize_posts' || action === 'kb_optimize_remove') {
|
||||
event.preventDefault();
|
||||
|
||||
const postIds = getSelectedPostIds();
|
||||
|
||||
if (postIds.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (action === 'kb_optimize_posts') {
|
||||
await handleBulkOptimize(postIds);
|
||||
} else if (action === 'kb_optimize_remove') {
|
||||
await handleBulkRemoveOptimization(postIds);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { updateLinkState } from '../optimizer/ui-manager.js';
|
||||
|
||||
/**
|
||||
* Find and update link state for a post.
|
||||
*
|
||||
* @param {number} postId - Post ID.
|
||||
* @param {{class: string, text: string}} state - UI state to set (e.g., UI_STATES.OPTIMIZE).
|
||||
*
|
||||
* @returns {HTMLElement|null} The link element if found.
|
||||
*/
|
||||
export function updatePostLinkState(postId, state) {
|
||||
const link = document.querySelector(`a[data-post-id="${postId}"]`);
|
||||
|
||||
if (link) {
|
||||
updateLinkState(link, state);
|
||||
}
|
||||
|
||||
return link;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all selected post IDs from checkboxes.
|
||||
*
|
||||
* @returns {number[]} Array of selected post IDs.
|
||||
*/
|
||||
export function getSelectedPostIds() {
|
||||
const checkboxes = document.querySelectorAll('input[name="post[]"]:checked');
|
||||
|
||||
return Array.from(checkboxes).map((checkbox) => parseInt(checkbox.value, 10));
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { UI_STATES } from '../optimizer/constants.js';
|
||||
import { AnimationManager } from './animation-manager.js';
|
||||
import { updatePostLinkState } from './link-manager.js';
|
||||
|
||||
/**
|
||||
* Initialize UI state for bulk operation.
|
||||
*
|
||||
* @param {number[]} postIds - Array of post IDs.
|
||||
* @param {AnimationManager} animationManager - Animation manager instance.
|
||||
*/
|
||||
export function initializeBulkUI(postIds, animationManager) {
|
||||
postIds.forEach((postId) => {
|
||||
const link = updatePostLinkState(postId, UI_STATES.OPTIMIZING);
|
||||
|
||||
if (link) {
|
||||
animationManager.start(postId, link);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export const NAME = 'kadence-optimizer';
|
||||
|
||||
// If you change this, the PHP version must match.
|
||||
export const META_KEY = '_kb_optimizer_status';
|
||||
|
||||
// An excluded post is stored in post meta as -1.
|
||||
export const STATUS_EXCLUDED = -1;
|
||||
export const STATUS_UNOPTIMIZED = 0;
|
||||
export const STATUS_OPTIMIZED = 1;
|
||||
@@ -0,0 +1,65 @@
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Button } from '@wordpress/components';
|
||||
import { external } from '@wordpress/icons';
|
||||
import { store as coreStore } from '@wordpress/core-data';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { store as preferencesStore } from '@wordpress/preferences';
|
||||
import { store as editorStore } from '@wordpress/editor';
|
||||
import { addQueryArgs } from '@wordpress/url';
|
||||
import { META_KEY, STATUS_OPTIMIZED } from '../meta/constants';
|
||||
|
||||
/**
|
||||
* Generate a link to view a post in its optimized state even if logged in.
|
||||
*/
|
||||
export default function OptimizedViewLink() {
|
||||
const { hasLoaded, permalink, isPublished, label, meta, showIconLabels } = useSelect((select) => {
|
||||
const editor = select(editorStore);
|
||||
const { get } = select(preferencesStore);
|
||||
|
||||
// Get post type for label.
|
||||
const postTypeSlug = editor.getCurrentPostType();
|
||||
const postType = select(coreStore).getPostType(postTypeSlug);
|
||||
const postTypeLabel = postType?.labels?.singular_name || 'Post';
|
||||
const dynamicLabel = __('View Optimized', 'kadence-blocks') + ' ' + postTypeLabel;
|
||||
|
||||
return {
|
||||
permalink: editor.getPermalink(),
|
||||
isPublished: editor.isCurrentPostPublished(),
|
||||
label: dynamicLabel,
|
||||
hasLoaded: !!postType,
|
||||
meta: editor.getEditedPostAttribute('meta'),
|
||||
showIconLabels: get('core', 'showIconLabels'),
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (!isPublished || !permalink || !hasLoaded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (meta !== undefined && meta[META_KEY] !== STATUS_OPTIMIZED) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const optimizerData = window.kbOptimizer || {};
|
||||
const nonce = optimizerData.token;
|
||||
|
||||
const url = addQueryArgs(permalink, {
|
||||
perf_token: nonce,
|
||||
kb_optimizer_preview: 1,
|
||||
});
|
||||
|
||||
return (
|
||||
<Button
|
||||
style={{ paddingLeft: 0 }}
|
||||
icon={external}
|
||||
iconPosition={'right'}
|
||||
label={label}
|
||||
href={url}
|
||||
target="_blank"
|
||||
showTooltip={!showIconLabels}
|
||||
size="compact"
|
||||
>
|
||||
{label}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import { analyzeWebsite, isSupported } from '@stellarwp/perf-analyzer-client';
|
||||
import { OPTIMIZE_ROUTE } from './constants';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
/**
|
||||
* Analyze a specific website URL and collect optimization data.
|
||||
*
|
||||
* @param {string} url - The URL to analyze.
|
||||
* @param {number} postId - The post ID for reference.
|
||||
* @param {string} postPath - The post path.
|
||||
* @param {string} nonce - The nonce value.
|
||||
*
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function analyzeSite(url, postId, postPath, nonce) {
|
||||
console.log(`🎯 Analyzing post ${postId}: ${url}`);
|
||||
|
||||
if (!isSupported()) {
|
||||
console.log('❌ Performance analysis not supported in this browser');
|
||||
throw new Error('Performance analysis not supported in this browser.');
|
||||
}
|
||||
|
||||
try {
|
||||
let results;
|
||||
|
||||
try {
|
||||
results = await analyzeWebsite(url, true, '[Kadence]', nonce);
|
||||
} catch (analysisError) {
|
||||
console.error('❌ Website analysis failed:', analysisError);
|
||||
throw analysisError;
|
||||
}
|
||||
|
||||
const res = await apiFetch({
|
||||
path: OPTIMIZE_ROUTE,
|
||||
method: 'POST',
|
||||
data: {
|
||||
post_path: postPath,
|
||||
post_id: postId,
|
||||
results,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(res);
|
||||
console.log(`✅ Analysis complete for post ID ${postId}:`, results);
|
||||
|
||||
// Send a request to generate a hash of the optimized page and don't wait for the results.
|
||||
// Log any network errors for debugging.
|
||||
void fetch(url + '?kadence_set_optimizer_hash=1', { credentials: 'omit', keepalive: true }).catch((error) => {
|
||||
console.error('❌ Failed to generate desktop hash:', error);
|
||||
});
|
||||
|
||||
// Send a request as a fake mobile device to generate the mobile hash.
|
||||
void fetch(url + '?kadence_set_optimizer_hash=1&kadence_is_mobile=1', {
|
||||
credentials: 'omit',
|
||||
keepalive: true,
|
||||
}).catch((error) => {
|
||||
console.error('❌ Failed to generate mobile hash:', error);
|
||||
});
|
||||
|
||||
return res;
|
||||
} catch (error) {
|
||||
console.error('❌ Analysis failed:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a post's optimization data.
|
||||
*
|
||||
* @param {number} postId The post ID to delete the optimization data for.
|
||||
* @param {string} postPath The relative path for the post.
|
||||
*
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function removeOptimization(postId, postPath) {
|
||||
try {
|
||||
return await apiFetch({
|
||||
path: OPTIMIZE_ROUTE,
|
||||
method: 'DELETE',
|
||||
data: {
|
||||
post_path: postPath,
|
||||
post_id: postId,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`❌ Failed to remove optimization for post ID ${postId}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
export const OPTIMIZE_ROUTE = '/kb-optimizer/v1/optimize';
|
||||
|
||||
// Get localized strings from WordPress.
|
||||
const l10n = window.kbOptimizerL10n || {};
|
||||
// Contains the perf_token.
|
||||
export const OPTIMIZER_DATA = window.kbOptimizer || {};
|
||||
|
||||
// UI State Constants.
|
||||
// If you update this, match to the Optimizer_Provider.php file.
|
||||
export const UI_STATES = {
|
||||
OPTIMIZE: {
|
||||
class: 'kb-optimize-post',
|
||||
text: l10n.runOptimizer || 'Run Optimizer',
|
||||
},
|
||||
REMOVE: {
|
||||
class: 'kb-remove-post-optimization',
|
||||
text: l10n.removeOptimization || 'Remove Optimization',
|
||||
},
|
||||
OPTIMIZING: {
|
||||
class: 'kb-optimizing',
|
||||
text: l10n.optimizing || 'Optimizing',
|
||||
},
|
||||
OPTIMIZED: {
|
||||
text: l10n.optimized || 'Optimized',
|
||||
},
|
||||
NOT_OPTIMIZED: {
|
||||
text: l10n.notOptimized || 'Not Optimized',
|
||||
},
|
||||
EXCLUDED: {
|
||||
text: l10n.excluded || 'Excluded',
|
||||
},
|
||||
NOT_OPTIMIZABLE: {
|
||||
text: l10n.notOptimizable || 'Not Optimizable',
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,178 @@
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { addAction } from '@wordpress/hooks';
|
||||
import { dispatch, select } from '@wordpress/data';
|
||||
import { getPathAndQueryString } from '@wordpress/url';
|
||||
import { analyzeSite, removeOptimization } from './analyzer.js';
|
||||
import { OPTIMIZER_DATA, UI_STATES } from './constants.js';
|
||||
import { createNotice, NOTICE_TYPES } from '@kadence-bundled/admin-notices';
|
||||
import { POST_SAVED_HOOK } from '../../../../../../src/extension/post-saved-event/constants';
|
||||
import { updateLinkState, getPostTitle, animateDots } from './ui-manager.js';
|
||||
import { META_KEY, STATUS_EXCLUDED } from '../meta/constants';
|
||||
|
||||
/**
|
||||
* Handle the post-save hook for automatic optimization.
|
||||
*/
|
||||
export function setupPostSaveHandler() {
|
||||
addAction(POST_SAVED_HOOK, 'kadence', async ({ post, permalink, suffix = '' }) => {
|
||||
console.log('Post Saved', post, permalink, suffix);
|
||||
|
||||
if (!permalink) {
|
||||
console.error('❌ No URL found for optimization. This is likely not a public post.');
|
||||
return;
|
||||
}
|
||||
|
||||
const postId = post.id;
|
||||
const postUrl = permalink;
|
||||
const postPath = getPathAndQueryString(permalink);
|
||||
const nonce = OPTIMIZER_DATA.token;
|
||||
|
||||
// This is a public post without a pretty rewrite rule.
|
||||
if (postPath.includes('?')) {
|
||||
console.error('❌ Unable to optimize. This is a public post, but has no rewrite rules.');
|
||||
return;
|
||||
}
|
||||
|
||||
const meta = select('core/editor').getEditedPostAttribute('meta');
|
||||
|
||||
if (meta !== undefined && meta[META_KEY] === STATUS_EXCLUDED) {
|
||||
console.warn(`⚠️ Post ID ${postId} was excluded from optimization via post meta.`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('🚀 Starting optimization...', { postId, postUrl, postPath });
|
||||
console.log(
|
||||
'%c' + 'Warnings are expected!',
|
||||
'color: #edd144; -webkit-text-stroke: 0.5px black; font-size: 28px; font-weight: bold;'
|
||||
);
|
||||
|
||||
try {
|
||||
const response = await analyzeSite(postUrl, postId, postPath, nonce);
|
||||
|
||||
console.log(response);
|
||||
|
||||
dispatch('core/notices').createSuccessNotice(__('Kadence Optimization Data Updated.', 'kadence-blocks'), {
|
||||
type: 'snackbar',
|
||||
});
|
||||
|
||||
// Force Gutenberg to reload the post + meta from the server so the View Optimized Page component can display in real-time.
|
||||
dispatch('core').invalidateResolution('getEntityRecord', ['postType', post.type, post.id]);
|
||||
} catch (error) {
|
||||
console.error('❌ Optimization failed:', error);
|
||||
|
||||
dispatch('core/notices').createErrorNotice(
|
||||
__('Failed to update Kadence Optimization Data.', 'kadence-blocks'),
|
||||
{
|
||||
type: 'snackbar',
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle optimize link click from the post list table.
|
||||
*
|
||||
* @param {Event} event - The click event
|
||||
*/
|
||||
export async function handleOptimizeClick(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const postUrl = event.target.dataset.postUrl;
|
||||
const postPath = event.target.dataset.postPath;
|
||||
|
||||
if (!postUrl) {
|
||||
console.error('❌ No URL found for optimization');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!postPath) {
|
||||
console.error('❌ No Post Path found for optimization');
|
||||
return;
|
||||
}
|
||||
|
||||
const postId = parseInt(event.target.dataset.postId, 10);
|
||||
const nonce = event.target.dataset.nonce;
|
||||
|
||||
console.log('🚀 Starting optimization...', { postId, postUrl });
|
||||
console.log(
|
||||
'%c' + 'Warnings are expected!',
|
||||
'color: #edd144; -webkit-text-stroke: 0.5px black; font-size: 28px; font-weight: bold;'
|
||||
);
|
||||
|
||||
// Show "Optimizing..." state with animated dots.
|
||||
updateLinkState(event.target, UI_STATES.OPTIMIZING);
|
||||
|
||||
const animationInterval = animateDots(event.target);
|
||||
const postTitle = getPostTitle(event.target, postId);
|
||||
|
||||
try {
|
||||
const response = await analyzeSite(postUrl, postId, postPath, nonce);
|
||||
console.log(response);
|
||||
|
||||
// Clear the animation interval.
|
||||
clearInterval(animationInterval);
|
||||
|
||||
// Update link state to show "Remove Optimization".
|
||||
updateLinkState(event.target, UI_STATES.REMOVE);
|
||||
|
||||
createNotice(
|
||||
sprintf(
|
||||
// translators: %s: The post title or post ID.
|
||||
__('🎉 "%s" is now Optimized!', 'kadence-blocks'),
|
||||
postTitle
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('❌ Optimization failed:', error);
|
||||
|
||||
// Clear the animation interval.
|
||||
clearInterval(animationInterval);
|
||||
|
||||
// Reset to original state.
|
||||
updateLinkState(event.target, UI_STATES.OPTIMIZE);
|
||||
|
||||
createNotice(
|
||||
// translators: %s: The post title or post ID.
|
||||
sprintf(__('Optimization failed for "%s"', 'kadence-blocks'), postTitle),
|
||||
NOTICE_TYPES.ERROR,
|
||||
true,
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle remove optimization link click in the post list table.
|
||||
*
|
||||
* @param {Event} event - The click event
|
||||
*/
|
||||
export async function handleRemoveOptimizationClick(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const postId = parseInt(event.target.dataset.postId, 10);
|
||||
const postPath = event.target.dataset.postPath;
|
||||
|
||||
console.log('Removing optimization...', { postId, postPath });
|
||||
|
||||
try {
|
||||
const response = await removeOptimization(postId, postPath);
|
||||
console.log(response);
|
||||
|
||||
// Update link state to show "Run Optimizer".
|
||||
updateLinkState(event.target, UI_STATES.OPTIMIZE);
|
||||
|
||||
const postTitle = getPostTitle(event.target, postId);
|
||||
|
||||
createNotice(
|
||||
sprintf(
|
||||
// translators: %s: The post title or post ID.
|
||||
__('Optimization data removed for "%s".', 'kadence-blocks'),
|
||||
postTitle
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to remove optimization:', error);
|
||||
|
||||
createNotice(__('An error occurred', 'kadence-blocks'), NOTICE_TYPES.ERROR, true, error);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { initOptimizer } from './initializer.js';
|
||||
import { initBulkActions } from '../bulk-actions';
|
||||
|
||||
// Initialize when DOM is ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initOptimizer();
|
||||
initBulkActions();
|
||||
});
|
||||
} else {
|
||||
initOptimizer();
|
||||
initBulkActions();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { setupPostSaveHandler, handleOptimizeClick, handleRemoveOptimizationClick } from './event-handlers.js';
|
||||
|
||||
/**
|
||||
* Initialize the optimizer.
|
||||
*/
|
||||
export function initOptimizer() {
|
||||
// Set up the post-save handler for automatic optimization.
|
||||
setupPostSaveHandler();
|
||||
|
||||
// Add click event listeners to all optimize links on the post list table.
|
||||
document.addEventListener('click', async function (event) {
|
||||
// Check if the clicked element is an optimize link.
|
||||
if (event.target.classList.contains('kb-optimize-post')) {
|
||||
await handleOptimizeClick(event);
|
||||
} else if (event.target.classList.contains('kb-remove-post-optimization')) {
|
||||
await handleRemoveOptimizationClick(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { UI_STATES } from './constants.js';
|
||||
|
||||
/**
|
||||
* Update the optimizer link state in the post list table.
|
||||
*
|
||||
* @param {HTMLElement} link - The link element to update.
|
||||
* @param {{class: string, text: string}} state - The state to set (OPTIMIZE, REMOVE, or OPTIMIZING).
|
||||
*/
|
||||
export function updateLinkState(link, state) {
|
||||
// Remove all state classes.
|
||||
link.className = link.className
|
||||
.replace(UI_STATES.OPTIMIZE.class, '')
|
||||
.replace(UI_STATES.REMOVE.class, '')
|
||||
.replace(UI_STATES.OPTIMIZING.class, '')
|
||||
.trim();
|
||||
|
||||
// Add the new state class.
|
||||
link.className += ' ' + state.class;
|
||||
|
||||
// Update text content.
|
||||
link.textContent = state.text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the post title from the row containing the given element in the post list table.
|
||||
*
|
||||
* @param {HTMLElement} element - The element to find the row for.
|
||||
* @param {number} postId - The post ID as fallback.
|
||||
*
|
||||
* @returns {string} The post title or fallback text.
|
||||
*/
|
||||
export function getPostTitle(element, postId) {
|
||||
const row = element.closest('tr');
|
||||
const rowTitle = row ? row.querySelector('.row-title')?.textContent?.trim() : null;
|
||||
|
||||
return rowTitle || `Post ID: ${postId}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Animate the dots in the "Optimizing" text.
|
||||
*
|
||||
* @param {HTMLElement} link - The link element to animate.
|
||||
*/
|
||||
export function animateDots(link) {
|
||||
let dots = 0;
|
||||
const baseText = link.textContent;
|
||||
|
||||
return setInterval(() => {
|
||||
dots = (dots + 1) % 4;
|
||||
const dotsText = '.'.repeat(dots);
|
||||
link.textContent = baseText + dotsText;
|
||||
}, 500);
|
||||
}
|
||||
Reference in New Issue
Block a user