Plugin Directory

Changeset 3416780


Ignore:
Timestamp:
12/10/2025 08:49:08 PM (4 months ago)
Author:
kakaroto84
Message:

2.1.0

Gutenberg Block Support

  • New: Native Gutenberg block for the block editor
  • New: Live preview in the block editor
  • New: Block sidebar controls for all settings
  • New: Support for wide and full width alignments
  • New: Server-side rendering for accurate previews
  • New: Dynamic category/tag/taxonomy loading
  • Improved: Better integration with WordPress 6.9 block editor

2.0.0

Major Update - Complete Redesign

  • New: Modern, AJAX-powered admin interface
  • New: Live shortcode preview feature
  • New: Tab-based navigation (Generator / Documentation)
  • New: Toast notifications for user feedback
  • New: CSS variables for easy customization
  • New: Grid-based responsive layouts
  • New: Improved accessibility (WCAG 2.1 compliant)
  • New: Support for WordPress admin color schemes
  • Improved: Complete CSS rewrite with modern standards
  • Improved: Better WooCommerce integration
  • Improved: Enhanced security with proper escaping and nonces
  • Improved: Code refactored using OOP principles
  • Improved: Performance optimizations
  • Improved: Mobile responsive design
  • Fixed: Various bug fixes and improvements
  • Compatibility: Tested with WordPress 6.9
  • Compatibility: Tested with PHP 8.0, 8.1, 8.2, and 8.3
Location:
pixel-clusters/trunk
Files:
6 added
5 edited

Legend:

Unmodified
Added
Removed
  • pixel-clusters/trunk/content/pixel-html.php

    r2175363 r3416780  
    1 <style>
    2 .wrap .postbox .inside h1
    3 {
    4     font-size:28px;
     1<?php
     2/**
     3 * Pixel Clusters - Admin Page Template
     4 * Version: 2.0.0
     5 * Modern AJAX-based admin interface
     6 */
     7
     8if ( ! defined( 'ABSPATH' ) ) {
     9    exit;
    510}
    611
    7 .wrap .postbox .inside h3#code-generate
    8 {
    9     display: inline-block;
    10     font-size: 18px;
    11 }
    12 
    13 .wrap .postbox .inside .mybutton
    14 {
    15     background: #09F;
    16     border: 1px solid black;
    17     border-radius: 10px;
    18     color: white;
    19     cursor: pointer;
    20     font-size: 18px;
    21     font-weight: 700;
    22     padding: 4px 15px;
    23 }
    24 
    25 .wrap .postbox .inside .mybutton:hover
    26 {
    27     background: black;
    28 }
    29 
    30 .wrap .postbox .inside #success
    31 {
    32     color: #0C3;
    33     font-style: 16px;
    34     font-style: italic;
    35 }
    36 
    37 </style>
    38 <div class="wrap">     
     12$types = pixel_cluster_post_type();
     13$has_woocommerce = class_exists( 'WooCommerce' );
     14?>
     15
     16<div class="pixel-clusters-wrap">
    3917   
    40     <div class="postbox">               
    41         <div class="inside">
    42 
    43             <h1>Pixel Clusters</h1>
    44             <hr />
    45             <h2>Copy Code:</h2>
    46             <h3 id="code-generate">[cluster]</h3> <button class="mybutton" onclick="pixel_copyToClipboard('#code-generate')">Copy</button> <span id="success"></span>
    47            
    48             <table width="100%" border="0" cellpadding="1" cellspacing="1">
    49             <tr>
    50                 <td width="15%">
    51                     <label for="mode">Select mode:</label>
    52                 </td>
    53                 <td>
    54                     <select id="mode" name="mode" onChange="pixel_cluster_code()">
    55                         <option disabled selected hidden>Select mode:</option>
    56                         <option value="1">Image, title and excerpt</option>
    57                         <option value="2">Image, title without excerpt</option>
    58                         <option value="3">List of titles</option>
    59                     </select>
    60                 </td>
    61             </tr>
    62             <tr>
    63                 <td width="15%">
    64                     <label for="number">Number of post to show:</label>
    65                 </td>
    66                 <td>
    67                     <input type="number" min="1" id="number" name="number" value="1" onChange="pixel_cluster_code()" />
    68                 </td>
    69             </tr>
    70             <tr>
    71                 <td width="15%">
    72                     <label for="type">Select type:</label>
    73                 </td>
    74                 <td>
    75                     <select id="type" name="type" onChange="pixel_cluster_change_type()">
    76                         <option disabled selected hidden>Select type:</option>
    77                         <option value="1">Categories</option>
    78                         <option value="2">Tags</option>
    79                     </select>
    80                 </td>
    81             </tr>
    82             <tr class="type-select" id="type1" style="display: none;">
    83                 <td width="15%">
    84                     <label for="cat">Select a category:</label>
    85                 </td>
    86                 <td>
    87                     <?php
    88                         wp_dropdown_categories( array(
    89                             'hide_empty'        => true,
    90                             'name'              => 'cat',
    91                             'id'                => 'cat',
    92                             'hierarchical'      => true,
    93                             'show_option_none'  => 'Select category',
    94                             'orderby'           => 'name',
    95                         ) );
    96                     ?>
    97                 </td>
    98             </tr>
    99             <tr class="type-select" id="type2" style="display: none;">
    100                 <td width="15%">
    101                     <label for="tag">Select a tag:</label>
    102                 </td>
    103                 <td>
    104                     <?php
    105                         wp_dropdown_categories( array(
    106                             'hide_empty'        => true,
    107                             'name'              => 'tag',
    108                             'id'                => 'tag',
    109                             'hierarchical'      => true,
    110                             'show_option_none'  => 'Select tag',
    111                             'taxonomy'          => 'post_tag',
    112                             'orderby'           => 'name',
    113                         ) );
    114                     ?>
    115                 </td>
    116             </tr>
    117             </table>   
    118            
    119             <p>&nbsp;</p>
    120            
    121             <p><strong>Example: [cluster type="1" tag_id="51"  modo="1" numero="2"]</strong></p>
    122            
    123             <h2>Type:</h2>
    124             <ul>
    125                 <li>1 - Category</li>
    126                 <li>2 - Tag</li>
    127             </ul>
    128            
    129             <h2>tag_id:</h2>
    130             <p>Reference ID</p>
    131            
    132            
    133             <h2>modo:</h2>
    134             <ul>
    135                 <li>1 - Image, title and excerpt</li>
    136                 <li>2 - Image, title without excerpt</li>
    137                 <li>3 - List of titles</li>
    138             </ul>
    139            
    140            
    141             <h2>numero:</h2>
    142             <p>Number of posts to show</p>
    143            
    144         </div>
    145     </div>
     18    <!-- Header -->
     19    <header class="pc-header">
     20        <div class="pc-header-content">
     21            <h1>
     22                <?php esc_html_e( 'Pixel Clusters', 'pixel-cluster' ); ?>
     23                <span class="pc-version">v<?php echo esc_html( PIXEL_CLUSTER_VERSION ); ?></span>
     24            </h1>
     25            <p><?php esc_html_e( 'Create beautiful post clusters with shortcodes', 'pixel-cluster' ); ?></p>
     26        </div>
     27        <div class="pc-header-icon">
     28            <span class="dashicons dashicons-grid-view"></span>
     29        </div>
     30    </header>
     31
     32    <!-- Tabs Navigation -->
     33    <div class="pc-tabs">
     34        <button type="button" class="pc-tab active" data-tab="pc-tab-generator">
     35            <span class="dashicons dashicons-shortcode"></span>
     36            <?php esc_html_e( 'Generator', 'pixel-cluster' ); ?>
     37        </button>
     38        <button type="button" class="pc-tab" data-tab="pc-tab-help">
     39            <span class="dashicons dashicons-editor-help"></span>
     40            <?php esc_html_e( 'Documentation', 'pixel-cluster' ); ?>
     41        </button>
     42    </div>
     43
     44    <!-- Tab: Generator -->
     45    <div id="pc-tab-generator" class="pc-tab-content active">
     46       
     47        <!-- Generated Code Card -->
     48        <div class="pc-card">
     49            <div class="pc-card-header">
     50                <span class="dashicons dashicons-shortcode"></span>
     51                <h2><?php esc_html_e( 'Generated Shortcode', 'pixel-cluster' ); ?></h2>
     52            </div>
     53            <div class="pc-card-body">
     54                <div class="pc-code-display">
     55                    <code id="pc-code-output" class="pc-code-output">[cluster]</code>
     56                    <button type="button" id="pc-copy-btn" class="pc-btn pc-btn-primary">
     57                        <span class="dashicons dashicons-clipboard"></span>
     58                        <?php esc_html_e( 'Copy', 'pixel-cluster' ); ?>
     59                    </button>
     60                    <span id="pc-success-message" class="pc-success-message">
     61                        <span class="dashicons dashicons-yes-alt"></span>
     62                        <?php esc_html_e( 'Copied!', 'pixel-cluster' ); ?>
     63                    </span>
     64                </div>
     65            </div>
     66        </div>
     67
     68        <!-- Configuration Card -->
     69        <div class="pc-card">
     70            <div class="pc-card-header">
     71                <span class="dashicons dashicons-admin-generic"></span>
     72                <h2><?php esc_html_e( 'Configuration', 'pixel-cluster' ); ?></h2>
     73            </div>
     74            <div class="pc-card-body">
     75                <div class="pc-form-grid">
     76                   
     77                    <!-- Display Mode -->
     78                    <div class="pc-form-group">
     79                        <label for="pc-mode">
     80                            <span class="dashicons dashicons-visibility"></span>
     81                            <?php esc_html_e( 'Display Mode', 'pixel-cluster' ); ?>
     82                        </label>
     83                        <select id="pc-mode" name="mode" class="pc-select">
     84                            <option value="1"><?php esc_html_e( 'Image, title and excerpt', 'pixel-cluster' ); ?></option>
     85                            <option value="2"><?php esc_html_e( 'Image and title (cards)', 'pixel-cluster' ); ?></option>
     86                            <option value="3"><?php esc_html_e( 'List of titles', 'pixel-cluster' ); ?></option>
     87                            <?php if ( $has_woocommerce ) : ?>
     88                            <option value="4"><?php esc_html_e( 'Products (WooCommerce)', 'pixel-cluster' ); ?></option>
     89                            <?php endif; ?>
     90                        </select>
     91                    </div>
     92
     93                    <!-- Number of posts -->
     94                    <div class="pc-form-group">
     95                        <label for="pc-number">
     96                            <span class="dashicons dashicons-editor-ol"></span>
     97                            <?php esc_html_e( 'Number of posts', 'pixel-cluster' ); ?>
     98                        </label>
     99                        <input type="number" id="pc-number" name="number" class="pc-input" value="4" min="1" max="50" />
     100                    </div>
     101
     102                    <!-- Content Type -->
     103                    <div class="pc-form-group">
     104                        <label for="pc-type">
     105                            <span class="dashicons dashicons-category"></span>
     106                            <?php esc_html_e( 'Content Type', 'pixel-cluster' ); ?>
     107                        </label>
     108                        <select id="pc-type" name="type" class="pc-select">
     109                            <option value="" disabled selected><?php esc_html_e( 'Select type...', 'pixel-cluster' ); ?></option>
     110                            <option value="1"><?php esc_html_e( 'Categories', 'pixel-cluster' ); ?></option>
     111                            <option value="2"><?php esc_html_e( 'Tags', 'pixel-cluster' ); ?></option>
     112                            <?php if ( ! empty( $types['name_type'] ) ) : ?>
     113                            <option value="3"><?php esc_html_e( 'Custom Post Type', 'pixel-cluster' ); ?></option>
     114                            <?php endif; ?>
     115                            <?php if ( $has_woocommerce ) : ?>
     116                            <option value="4"><?php esc_html_e( 'WooCommerce Categories', 'pixel-cluster' ); ?></option>
     117                            <option value="5"><?php esc_html_e( 'WooCommerce Tags', 'pixel-cluster' ); ?></option>
     118                            <?php endif; ?>
     119                        </select>
     120                    </div>
     121
     122                </div>
     123
     124                <!-- Dynamic Fields Container -->
     125                <div class="pc-form-grid" style="margin-top: 20px;">
     126                   
     127                    <!-- Categories Field -->
     128                    <div id="pc-field-cat" class="pc-form-group pc-dynamic-field">
     129                        <label for="pc-cat">
     130                            <span class="dashicons dashicons-category"></span>
     131                            <?php esc_html_e( 'Select Category', 'pixel-cluster' ); ?>
     132                        </label>
     133                        <?php
     134                        wp_dropdown_categories( array(
     135                            'hide_empty'       => true,
     136                            'name'             => 'cat',
     137                            'id'               => 'pc-cat',
     138                            'class'            => 'pc-select',
     139                            'hierarchical'     => true,
     140                            'show_option_none' => __( 'All categories', 'pixel-cluster' ),
     141                            'orderby'          => 'name',
     142                        ) );
     143                        ?>
     144                    </div>
     145
     146                    <!-- Tags Field -->
     147                    <div id="pc-field-tag" class="pc-form-group pc-dynamic-field">
     148                        <label for="pc-tag">
     149                            <span class="dashicons dashicons-tag"></span>
     150                            <?php esc_html_e( 'Select Tag', 'pixel-cluster' ); ?>
     151                        </label>
     152                        <?php
     153                        wp_dropdown_categories( array(
     154                            'hide_empty'       => true,
     155                            'name'             => 'tag',
     156                            'id'               => 'pc-tag',
     157                            'class'            => 'pc-select',
     158                            'hierarchical'     => true,
     159                            'show_option_none' => __( 'All tags', 'pixel-cluster' ),
     160                            'taxonomy'         => 'post_tag',
     161                            'orderby'          => 'name',
     162                        ) );
     163                        ?>
     164                    </div>
     165
     166                    <!-- Post Type Field -->
     167                    <div id="pc-field-post-type" class="pc-form-group pc-dynamic-field">
     168                        <label for="pc-post-type">
     169                            <span class="dashicons dashicons-admin-post"></span>
     170                            <?php esc_html_e( 'Select Post Type', 'pixel-cluster' ); ?>
     171                        </label>
     172                        <select name="post_type" id="pc-post-type" class="pc-select">
     173                            <option value="-1"><?php esc_html_e( 'Select post type...', 'pixel-cluster' ); ?></option>
     174                            <?php foreach ( $types['name_type'] as $type ) : ?>
     175                            <option value="<?php echo esc_attr( $type ); ?>"><?php echo esc_html( $type ); ?></option>
     176                            <?php endforeach; ?>
     177                        </select>
     178                    </div>
     179
     180                    <!-- WooCommerce Categories -->
     181                    <?php if ( $has_woocommerce ) : ?>
     182                    <div id="pc-field-product-cat" class="pc-form-group pc-dynamic-field">
     183                        <label for="pc-product-cat">
     184                            <span class="dashicons dashicons-cart"></span>
     185                            <?php esc_html_e( 'Product Category', 'pixel-cluster' ); ?>
     186                        </label>
     187                        <?php
     188                        wp_dropdown_categories( array(
     189                            'hide_empty'       => true,
     190                            'name'             => 'product_cat',
     191                            'id'               => 'pc-product-cat',
     192                            'class'            => 'pc-select',
     193                            'hierarchical'     => true,
     194                            'show_option_none' => __( 'All categories', 'pixel-cluster' ),
     195                            'taxonomy'         => 'product_cat',
     196                            'orderby'          => 'name',
     197                        ) );
     198                        ?>
     199                    </div>
     200
     201                    <!-- WooCommerce Tags -->
     202                    <div id="pc-field-product-tag" class="pc-form-group pc-dynamic-field">
     203                        <label for="pc-product-tag">
     204                            <span class="dashicons dashicons-tag"></span>
     205                            <?php esc_html_e( 'Product Tag', 'pixel-cluster' ); ?>
     206                        </label>
     207                        <?php
     208                        wp_dropdown_categories( array(
     209                            'hide_empty'       => true,
     210                            'name'             => 'product_tag',
     211                            'id'               => 'pc-product-tag',
     212                            'class'            => 'pc-select',
     213                            'hierarchical'     => true,
     214                            'show_option_none' => __( 'All tags', 'pixel-cluster' ),
     215                            'taxonomy'         => 'product_tag',
     216                            'orderby'          => 'name',
     217                        ) );
     218                        ?>
     219                    </div>
     220                    <?php endif; ?>
     221
     222                </div>
     223
     224                <!-- Custom Post Type Taxonomies -->
     225                <?php foreach ( $types['name_type'] as $type ) : ?>
     226                    <?php if ( ! empty( $types['name_taxonomies'][ $type ] ) ) : ?>
     227                   
     228                    <div id="pc-field-taxonomy-<?php echo esc_attr( $type ); ?>" class="pc-form-group pc-dynamic-field pc-field-taxonomy" style="margin-top: 20px;">
     229                        <label for="pc-taxonomy-<?php echo esc_attr( $type ); ?>">
     230                            <span class="dashicons dashicons-portfolio"></span>
     231                            <?php esc_html_e( 'Select Taxonomy', 'pixel-cluster' ); ?>
     232                        </label>
     233                        <select name="taxonomy_<?php echo esc_attr( $type ); ?>" id="pc-taxonomy-<?php echo esc_attr( $type ); ?>" class="pc-select pc-taxonomy-select">
     234                            <option value="-1"><?php esc_html_e( 'Select taxonomy...', 'pixel-cluster' ); ?></option>
     235                            <?php foreach ( $types['name_taxonomies'][ $type ] as $taxonomy ) : ?>
     236                            <option value="<?php echo esc_attr( $taxonomy ); ?>"><?php echo esc_html( $taxonomy ); ?></option>
     237                            <?php endforeach; ?>
     238                        </select>
     239                    </div>
     240                   
     241                    <?php foreach ( $types['name_taxonomies'][ $type ] as $taxonomy ) : ?>
     242                    <div id="pc-field-term-<?php echo esc_attr( $taxonomy ); ?>" class="pc-form-group pc-dynamic-field pc-field-term" style="margin-top: 20px;">
     243                        <label for="pc-term-<?php echo esc_attr( $taxonomy ); ?>">
     244                            <span class="dashicons dashicons-list-view"></span>
     245                            <?php esc_html_e( 'Select Term', 'pixel-cluster' ); ?>
     246                        </label>
     247                        <?php
     248                        wp_dropdown_categories( array(
     249                            'hide_empty'       => true,
     250                            'name'             => 'term_' . $taxonomy,
     251                            'id'               => 'pc-term-' . $taxonomy,
     252                            'class'            => 'pc-select pc-term-select',
     253                            'hierarchical'     => true,
     254                            'show_option_none' => __( 'All terms', 'pixel-cluster' ),
     255                            'taxonomy'         => $taxonomy,
     256                            'orderby'          => 'name',
     257                        ) );
     258                        ?>
     259                    </div>
     260                    <?php endforeach; ?>
     261                   
     262                    <?php endif; ?>
     263                <?php endforeach; ?>
     264
     265                <!-- Preview Section -->
     266                <div class="pc-preview-section">
     267                    <div class="pc-preview-header">
     268                        <h3>
     269                            <span class="dashicons dashicons-visibility"></span>
     270                            <?php esc_html_e( 'Preview', 'pixel-cluster' ); ?>
     271                        </h3>
     272                        <button type="button" id="pc-preview-btn" class="pc-btn pc-btn-secondary">
     273                            <span class="dashicons dashicons-update"></span>
     274                            <?php esc_html_e( 'Load Preview', 'pixel-cluster' ); ?>
     275                        </button>
     276                    </div>
     277                    <div id="pc-preview-container" class="pc-preview-container">
     278                        <div class="pc-preview-placeholder">
     279                            <span class="dashicons dashicons-admin-appearance"></span>
     280                            <p><?php esc_html_e( 'Configure your shortcode and click "Load Preview" to see the result', 'pixel-cluster' ); ?></p>
     281                        </div>
     282                    </div>
     283                </div>
     284
     285            </div>
     286        </div>
     287
     288    </div>
     289
     290    <!-- Tab: Help -->
     291    <div id="pc-tab-help" class="pc-tab-content">
     292       
     293        <div class="pc-card">
     294            <div class="pc-card-header">
     295                <span class="dashicons dashicons-book"></span>
     296                <h2><?php esc_html_e( 'How to Use', 'pixel-cluster' ); ?></h2>
     297            </div>
     298            <div class="pc-card-body">
     299               
     300                <div class="pc-example-box">
     301                    <h4>
     302                        <span class="dashicons dashicons-info"></span>
     303                        <?php esc_html_e( 'Example Shortcode', 'pixel-cluster' ); ?>
     304                    </h4>
     305                    <code>[cluster type="1" tag_id="5" modo="1" numero="4"]</code>
     306                </div>
     307
     308                <div class="pc-help-grid" style="margin-top: 24px;">
     309                   
     310                    <div class="pc-help-item">
     311                        <h4>type</h4>
     312                        <p><?php esc_html_e( 'Content source type', 'pixel-cluster' ); ?></p>
     313                        <ul>
     314                            <li><strong>1</strong> - <?php esc_html_e( 'Categories', 'pixel-cluster' ); ?></li>
     315                            <li><strong>2</strong> - <?php esc_html_e( 'Tags', 'pixel-cluster' ); ?></li>
     316                            <li><strong>3</strong> - <?php esc_html_e( 'Custom Post Type', 'pixel-cluster' ); ?></li>
     317                            <li><strong>4</strong> - <?php esc_html_e( 'WooCommerce Categories', 'pixel-cluster' ); ?></li>
     318                            <li><strong>5</strong> - <?php esc_html_e( 'WooCommerce Tags', 'pixel-cluster' ); ?></li>
     319                        </ul>
     320                    </div>
     321
     322                    <div class="pc-help-item">
     323                        <h4>tag_id</h4>
     324                        <p><?php esc_html_e( 'The ID of the category, tag, or term to filter by. Leave empty to show all.', 'pixel-cluster' ); ?></p>
     325                    </div>
     326
     327                    <div class="pc-help-item">
     328                        <h4>modo</h4>
     329                        <p><?php esc_html_e( 'Display mode for the cluster', 'pixel-cluster' ); ?></p>
     330                        <ul>
     331                            <li><strong>1</strong> - <?php esc_html_e( 'Image, title and excerpt', 'pixel-cluster' ); ?></li>
     332                            <li><strong>2</strong> - <?php esc_html_e( 'Image and title (cards)', 'pixel-cluster' ); ?></li>
     333                            <li><strong>3</strong> - <?php esc_html_e( 'List of titles only', 'pixel-cluster' ); ?></li>
     334                            <li><strong>4</strong> - <?php esc_html_e( 'Products with price and cart button', 'pixel-cluster' ); ?></li>
     335                        </ul>
     336                    </div>
     337
     338                    <div class="pc-help-item">
     339                        <h4>numero</h4>
     340                        <p><?php esc_html_e( 'Number of posts to display. Default is 4.', 'pixel-cluster' ); ?></p>
     341                    </div>
     342
     343                    <div class="pc-help-item">
     344                        <h4>post_type</h4>
     345                        <p><?php esc_html_e( 'For custom post types (type="3"), specify the post type slug.', 'pixel-cluster' ); ?></p>
     346                    </div>
     347
     348                    <div class="pc-help-item">
     349                        <h4>taxonomy</h4>
     350                        <p><?php esc_html_e( 'For custom post types, specify the taxonomy to filter by.', 'pixel-cluster' ); ?></p>
     351                    </div>
     352
     353                </div>
     354
     355            </div>
     356        </div>
     357
     358        <!-- Quick Start Guide -->
     359        <div class="pc-card">
     360            <div class="pc-card-header">
     361                <span class="dashicons dashicons-welcome-learn-more"></span>
     362                <h2><?php esc_html_e( 'Quick Start Guide', 'pixel-cluster' ); ?></h2>
     363            </div>
     364            <div class="pc-card-body">
     365                <ol style="line-height: 2; padding-left: 20px;">
     366                    <li><?php esc_html_e( 'Go to the Generator tab', 'pixel-cluster' ); ?></li>
     367                    <li><?php esc_html_e( 'Select a display mode (how the posts will look)', 'pixel-cluster' ); ?></li>
     368                    <li><?php esc_html_e( 'Choose the number of posts to show', 'pixel-cluster' ); ?></li>
     369                    <li><?php esc_html_e( 'Select the content type (categories, tags, etc.)', 'pixel-cluster' ); ?></li>
     370                    <li><?php esc_html_e( 'Optionally filter by a specific category or tag', 'pixel-cluster' ); ?></li>
     371                    <li><?php esc_html_e( 'Click "Copy" to copy the shortcode', 'pixel-cluster' ); ?></li>
     372                    <li><?php esc_html_e( 'Paste the shortcode in any post, page, or widget', 'pixel-cluster' ); ?></li>
     373                </ol>
     374            </div>
     375        </div>
     376
     377    </div>
    146378
    147379</div>
     380
     381<!-- Toast Notification Container -->
     382<div id="pc-toast" class="pc-toast"></div>
  • pixel-clusters/trunk/css/pixel-clusters.css

    r2144690 r3416780  
    1 /* CSS Document */
    2 
    3 .cluster
    4 {
    5     display: inline-block;
    6     padding: 10px 0 10px 0;
    7     vertical-align: top;
    8     width: 100%;
    9 }
    10 
    11 .cluster .category
    12 {
    13     border-bottom: 1px dashed #CCC;
    14     display: inline-block;
    15     margin-bottom: 30px;
    16     padding-bottom: 10px;
    17     vertical-align: top;
    18     width: 100%;
    19 }
    20 
    21 .cluster .category .articles
    22 {
    23     float: left;
    24     font-size: 1em;
    25     font-weight: 400;
    26     line-height: normal;
    27     padding: 0 0 0 1%;
    28     width: 79%;
    29 }
    30 
    31 .cluster .category .articles .h2
    32 {
    33     font-size: 1.2em;
    34     font-weight: 700;
    35     line-height: normal;
    36     margin: 0 0 10px 0;
    37 }
    38 
    39 .cluster .category .images
    40 {
    41     float: left;
    42     padding: 0 2% 0 0;
    43     width: 18%;
    44 }
    45 
    46 .cluster .category .images img
    47 {
    48     height: auto;
    49     max-width: 100%;
    50 }
    51 
    52 .cluster .column3
    53 {
    54     display: inline-block;
    55     margin-bottom: 50px;
    56     padding: 0 1.5%;
    57     vertical-align: top;
    58     width: 30%;
    59 }
    60 
    61 .cluster .column3 .articles
    62 {
    63     font-size: 0.8em;
    64     font-weight: 400;
    65     line-height: normal;
    66     margin-top: 10px;
    67 }
    68 
    69 .cluster .column3 .articles .h2 a
    70 {
     1/**
     2 * Pixel Clusters - Frontend Styles
     3 * Version: 2.0.0
     4 * Modern responsive CSS for post clusters
     5 */
     6
     7/* CSS Variables */
     8:root {
     9    --pc-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
     10    --pc-primary-color: #2271b1;
     11    --pc-text-color: #1d2327;
     12    --pc-text-secondary: #50575e;
     13    --pc-bg-light: #f6f7f7;
     14    --pc-border-color: #dcdcde;
     15    --pc-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
     16    --pc-shadow-hover: 0 4px 16px rgba(0, 0, 0, 0.12);
     17    --pc-radius: 8px;
     18    --pc-transition: all 0.3s ease;
     19    --pc-btn-primary: #9bad3e;
     20    --pc-btn-cart: #e96656;
     21    --pc-btn-hover: #666666;
     22}
     23
     24/* Base Cluster Container */
     25.cluster,
     26.pc-cluster {
     27    display: grid;
     28    gap: 24px;
     29    padding: 16px 0;
     30    width: 100%;
     31}
     32
     33/* Legacy support */
     34.cluster {
     35    display: inline-block;
     36    padding: 10px 0;
     37    vertical-align: top;
     38    width: 100%;
     39}
     40
     41/* Mode 1: Full articles with image, title, excerpt */
     42.cluster .category,
     43.pc-cluster .pc-item--full {
     44    display: grid;
     45    grid-template-columns: 120px 1fr;
     46    gap: 20px;
     47    align-items: start;
     48    padding: 20px;
     49    background: var(--pc-bg-light);
     50    border-radius: var(--pc-radius);
     51    border: 1px solid var(--pc-border-color);
     52    transition: var(--pc-transition);
     53}
     54
     55.cluster .category:hover,
     56.pc-cluster .pc-item--full:hover {
     57    box-shadow: var(--pc-shadow-hover);
     58    transform: translateY(-2px);
     59}
     60
     61/* Legacy support for category */
     62.cluster .category {
     63    border-bottom: 1px dashed #CCC;
     64    display: inline-block;
     65    margin-bottom: 30px;
     66    padding-bottom: 10px;
     67    vertical-align: top;
     68    width: 100%;
     69}
     70
     71.cluster .category .articles,
     72.pc-cluster .pc-item--full .pc-item__content {
    7173    font-size: 1em;
    72     font-weight: 700;
    73     line-height: normal;
    74 }
    75 
    76 .cluster .column3 .images
    77 {
    78     background-position: center center;
    79     background-repeat: no-repeat;
    80     background-size: cover;
    81     height: 150px;
    82     width: 100%;
    83 }
     74    font-weight: 400;
     75    line-height: 1.6;
     76}
     77
     78/* Legacy float support */
     79.cluster .category .articles {
     80    float: left;
     81    padding: 0 0 0 1%;
     82    width: 79%;
     83}
     84
     85.cluster .category .articles .h2,
     86.pc-cluster .pc-item__title {
     87    font-size: 1.15em;
     88    font-weight: 600;
     89    line-height: 1.4;
     90    margin: 0 0 8px 0;
     91}
     92
     93.cluster .category .articles .h2 a,
     94.pc-cluster .pc-item__title a {
     95    color: var(--pc-text-color);
     96    text-decoration: none;
     97    transition: var(--pc-transition);
     98}
     99
     100.cluster .category .articles .h2 a:hover,
     101.pc-cluster .pc-item__title a:hover {
     102    color: var(--pc-primary-color);
     103}
     104
     105.cluster .category .images,
     106.pc-cluster .pc-item--full .pc-item__image {
     107    border-radius: var(--pc-radius);
     108    overflow: hidden;
     109}
     110
     111/* Legacy float support */
     112.cluster .category .images {
     113    float: left;
     114    padding: 0 2% 0 0;
     115    width: 18%;
     116}
     117
     118.cluster .category .images img,
     119.pc-cluster .pc-item__img {
     120    height: auto;
     121    max-width: 100%;
     122    border-radius: var(--pc-radius);
     123    object-fit: cover;
     124    transition: var(--pc-transition);
     125}
     126
     127.cluster .category .images a:hover img,
     128.pc-cluster .pc-item--full a:hover .pc-item__img {
     129    transform: scale(1.05);
     130}
     131
     132.pc-cluster .pc-item__excerpt {
     133    color: var(--pc-text-secondary);
     134    font-size: 0.95em;
     135    line-height: 1.6;
     136}
     137
     138/* Mode 2: Cards with image and title */
     139.cluster .column3,
     140.pc-cluster .pc-item--card {
     141    display: inline-block;
     142    width: calc(33.333% - 20px);
     143    margin: 0 10px 24px;
     144    vertical-align: top;
     145    background: #fff;
     146    border-radius: var(--pc-radius);
     147    overflow: hidden;
     148    box-shadow: var(--pc-shadow);
     149    transition: var(--pc-transition);
     150}
     151
     152.cluster .column3:hover,
     153.pc-cluster .pc-item--card:hover {
     154    box-shadow: var(--pc-shadow-hover);
     155    transform: translateY(-4px);
     156}
     157
     158.cluster .column3 .articles,
     159.pc-cluster .pc-item--card .pc-item__content {
     160    padding: 16px;
     161}
     162
     163.cluster .column3 .articles .h2,
     164.pc-cluster .pc-item--card .pc-item__title {
     165    margin: 0;
     166}
     167
     168.cluster .column3 .articles .h2 a,
     169.pc-cluster .pc-item--card .pc-item__title a {
     170    font-size: 0.95em;
     171    font-weight: 600;
     172    line-height: 1.4;
     173    display: block;
     174    color: var(--pc-text-color);
     175    text-decoration: none;
     176}
     177
     178.cluster .column3 .articles .h2 a:hover,
     179.pc-cluster .pc-item--card .pc-item__title a:hover {
     180    color: var(--pc-primary-color);
     181}
     182
     183.cluster .column3 .images,
     184.pc-cluster .pc-item--card .pc-item__image-bg,
     185.pc-cluster .pc-item--card .images {
     186    background-position: center center;
     187    background-repeat: no-repeat;
     188    background-size: cover;
     189    height: 180px;
     190    width: 100%;
     191    transition: var(--pc-transition);
     192}
     193
     194.cluster .column3:hover .images,
     195.pc-cluster .pc-item--card:hover .pc-item__image-bg {
     196    transform: scale(1.05);
     197}
     198
     199.cluster .column3 .articles .price,
     200.pc-cluster .pc-item--card .pc-item__price {
     201    margin: 10px 0;
     202    font-weight: 600;
     203    color: var(--pc-primary-color);
     204}
     205
     206/* Mode 3: List style */
     207.pc-cluster--mode-3 {
     208    list-style: none;
     209    padding: 0;
     210    margin: 0;
     211}
     212
     213.pc-cluster .pc-item--list,
     214.cluster li {
     215    padding: 12px 16px;
     216    border-bottom: 1px solid var(--pc-border-color);
     217    transition: var(--pc-transition);
     218}
     219
     220.pc-cluster .pc-item--list:last-child,
     221.cluster li:last-child {
     222    border-bottom: none;
     223}
     224
     225.pc-cluster .pc-item--list:hover,
     226.cluster li:hover {
     227    background: var(--pc-bg-light);
     228    padding-left: 24px;
     229}
     230
     231.pc-cluster .pc-item--list a,
     232.cluster li a {
     233    color: var(--pc-text-color);
     234    text-decoration: none;
     235    font-weight: 500;
     236    transition: var(--pc-transition);
     237}
     238
     239.pc-cluster .pc-item--list a:hover,
     240.cluster li a:hover {
     241    color: var(--pc-primary-color);
     242}
     243
     244/* Mode 4: Products (Flex layout) */
     245.cluster.flex,
     246.pc-cluster--flex {
     247    display: flex;
     248    flex-wrap: wrap;
     249    gap: 24px;
     250}
     251
     252.cluster.flex .columns,
     253.pc-cluster .pc-item--product {
     254    flex: 1 1 calc(33.333% - 24px);
     255    min-width: 280px;
     256    max-width: calc(33.333% - 16px);
     257    background: #fff;
     258    border-radius: var(--pc-radius);
     259    overflow: hidden;
     260    box-shadow: var(--pc-shadow);
     261    transition: var(--pc-transition);
     262}
     263
     264.cluster.flex .columns:hover,
     265.pc-cluster .pc-item--product:hover {
     266    box-shadow: var(--pc-shadow-hover);
     267    transform: translateY(-4px);
     268}
     269
     270.cluster.flex .columns .articles,
     271.pc-cluster .pc-item--product .pc-item__content {
     272    padding: 16px;
     273}
     274
     275.cluster.flex .columns .articles .h2,
     276.pc-cluster .pc-item--product .pc-item__title {
     277    margin: 0 0 8px 0;
     278}
     279
     280.cluster.flex .columns .articles .h2 a,
     281.pc-cluster .pc-item--product .pc-item__title a {
     282    font-size: 1em;
     283    font-weight: 600;
     284    line-height: 1.4;
     285    color: var(--pc-text-color);
     286    text-decoration: none;
     287}
     288
     289.cluster.flex .columns .articles .h2 a:hover,
     290.pc-cluster .pc-item--product .pc-item__title a:hover {
     291    color: var(--pc-primary-color);
     292}
     293
     294.cluster.flex .columns .articles .price,
     295.pc-cluster .pc-item--product .pc-item__price {
     296    margin: 12px 0;
     297    font-size: 1.1em;
     298    font-weight: 700;
     299    color: var(--pc-primary-color);
     300}
     301
     302.cluster.flex .columns .buttons,
     303.pc-cluster .pc-item--product .pc-item__buttons {
     304    display: flex;
     305    gap: 8px;
     306    width: 100%;
     307}
     308
     309.cluster.flex .columns .buttons a,
     310.cluster.flex .columns .buttons button,
     311.pc-cluster .pc-item--product .pc-btn {
     312    flex: 1;
     313    background: var(--pc-btn-primary);
     314    border: none;
     315    border-radius: var(--pc-radius);
     316    color: white;
     317    display: flex;
     318    align-items: center;
     319    justify-content: center;
     320    font-weight: 600;
     321    font-size: 0.85em;
     322    padding: 12px 8px;
     323    text-align: center;
     324    text-transform: uppercase;
     325    text-decoration: none;
     326    cursor: pointer;
     327    transition: var(--pc-transition);
     328}
     329
     330.cluster.flex .columns .buttons a.carrito,
     331.cluster.flex .columns .buttons button.carrito,
     332.pc-cluster .pc-item--product .pc-btn--cart {
     333    background: var(--pc-btn-cart);
     334}
     335
     336.cluster.flex .columns .buttons a:hover,
     337.cluster.flex .columns .buttons button:hover,
     338.pc-cluster .pc-item--product .pc-btn:hover {
     339    background: var(--pc-btn-hover);
     340    transform: translateY(-2px);
     341}
     342
     343.cluster.flex .columns .images,
     344.pc-cluster .pc-item--product .pc-item__image-bg,
     345.pc-cluster .pc-item--product .images {
     346    background-position: center center;
     347    background-repeat: no-repeat;
     348    background-size: cover;
     349    height: 200px;
     350    width: 100%;
     351    transition: var(--pc-transition);
     352}
     353
     354.cluster.flex .columns:hover .images,
     355.pc-cluster .pc-item--product:hover .pc-item__image-bg {
     356    transform: scale(1.05);
     357}
     358
     359/* Image link wrapper */
     360.pc-item__image-link {
     361    display: block;
     362    overflow: hidden;
     363}
     364
     365/* Loading state for buttons */
     366.pc-btn.loading {
     367    position: relative;
     368    color: transparent;
     369    pointer-events: none;
     370}
     371
     372.pc-btn.loading::after {
     373    content: '';
     374    position: absolute;
     375    width: 16px;
     376    height: 16px;
     377    border: 2px solid #fff;
     378    border-top-color: transparent;
     379    border-radius: 50%;
     380    animation: spin 0.8s linear infinite;
     381}
     382
     383@keyframes spin {
     384    to { transform: rotate(360deg); }
     385}
     386
     387/* Placeholder for missing images */
     388.pc-item__placeholder {
     389    display: block;
     390    width: 100%;
     391    height: 120px;
     392    background: linear-gradient(135deg, #f0f0f0 0%, #e0e0e0 100%);
     393    border-radius: var(--pc-radius);
     394}
     395
     396/* Price formatting */
     397.pc-price {
     398    font-weight: 700;
     399    color: var(--pc-primary-color);
     400}
     401
     402/* Responsive Design */
     403@media screen and (max-width: 1024px) {
     404    .cluster .column3,
     405    .pc-cluster .pc-item--card {
     406        width: calc(50% - 20px);
     407    }
     408   
     409    .cluster.flex .columns,
     410    .pc-cluster .pc-item--product {
     411        flex: 1 1 calc(50% - 12px);
     412        max-width: calc(50% - 12px);
     413    }
     414}
     415
     416@media screen and (max-width: 768px) {
     417    .cluster .category,
     418    .pc-cluster .pc-item--full {
     419        grid-template-columns: 100px 1fr;
     420        gap: 16px;
     421        padding: 16px;
     422    }
     423   
     424    /* Legacy float override for mobile */
     425    .cluster .category .images {
     426        float: none;
     427        width: 100px;
     428        padding: 0;
     429    }
     430   
     431    .cluster .category .articles {
     432        float: none;
     433        width: 100%;
     434        padding: 0;
     435    }
     436   
     437    .cluster .column3,
     438    .pc-cluster .pc-item--card {
     439        width: 100%;
     440        margin: 0 0 16px 0;
     441    }
     442   
     443    .cluster.flex,
     444    .pc-cluster--flex {
     445        gap: 16px;
     446    }
     447   
     448    .cluster.flex .columns,
     449    .pc-cluster .pc-item--product {
     450        flex: 1 1 100%;
     451        max-width: 100%;
     452        min-width: auto;
     453    }
     454}
     455
     456@media screen and (max-width: 480px) {
     457    .cluster .category,
     458    .pc-cluster .pc-item--full {
     459        grid-template-columns: 1fr;
     460        text-align: center;
     461    }
     462   
     463    .cluster .category .images,
     464    .pc-cluster .pc-item--full .pc-item__image {
     465        margin: 0 auto;
     466        max-width: 150px;
     467    }
     468   
     469    .cluster.flex .columns .buttons,
     470    .pc-cluster .pc-item--product .pc-item__buttons {
     471        flex-direction: column;
     472    }
     473   
     474    .cluster.flex .columns .buttons a,
     475    .cluster.flex .columns .buttons button,
     476    .pc-cluster .pc-item--product .pc-btn {
     477        width: 100%;
     478    }
     479}
     480
     481/* Print Styles */
     482@media print {
     483    .cluster,
     484    .pc-cluster {
     485        display: block;
     486    }
     487   
     488    .cluster .column3,
     489    .cluster.flex .columns,
     490    .pc-cluster .pc-item--card,
     491    .pc-cluster .pc-item--product {
     492        box-shadow: none;
     493        border: 1px solid #ddd;
     494        page-break-inside: avoid;
     495    }
     496   
     497    .cluster.flex .columns .buttons,
     498    .pc-cluster .pc-item--product .pc-item__buttons {
     499        display: none;
     500    }
     501}
     502
     503/* Accessibility */
     504.cluster a:focus,
     505.pc-cluster a:focus,
     506.cluster button:focus,
     507.pc-cluster button:focus {
     508    outline: 2px solid var(--pc-primary-color);
     509    outline-offset: 2px;
     510}
     511
     512/* Reduced motion support */
     513@media (prefers-reduced-motion: reduce) {
     514    .cluster *,
     515    .pc-cluster * {
     516        transition: none !important;
     517        animation: none !important;
     518    }
     519}
  • pixel-clusters/trunk/js/cluster.js

    r3405223 r3416780  
    33jQuery(document).ready(function(){
    44   
    5     jQuery('#cat').on('change', function() {
    6         pixel_cluster_code();
    7     });
    8    
    9     jQuery('#tag').on('change', function() {
     5    jQuery('select').on('change', function() {
    106        pixel_cluster_code();
    117    });
    128
    139});
     10
     11function pixel_cluster_buy_product( product_id )
     12{
     13    jQuery.get( '/wp-admin/admin-ajax.php' , {
     14        'action' : 'woocommerce_ajax_add_to_cart',
     15        'product_id' : product_id
     16        }).done(function( data ) {                                     
     17           
     18            if(data == 'FAIL')
     19            {               
     20                jQuery('#error-buy').html('<div class="error">We Have Experienced Technical Difficulties, Try again later.</div>');
     21            }
     22            else if(data == 'DUPLICATE')
     23            {
     24                jQuery('#error-buy').html('<div class="error">You can not buy more than one test. Please proceed to cart.</div>');
     25            }
     26            else
     27            {
     28                location.href = '/carro/';
     29            }                                           
     30    });
     31}
     32
     33function pixel_cluster_change_post()
     34{
     35    var post_type = jQuery('#post_type').val();
     36   
     37    jQuery('.type-post').hide();
     38   
     39    jQuery('#type-'+ post_type).fadeIn();
     40   
     41    pixel_cluster_code();
     42}
     43
     44function pixel_cluster_change_taxonomy()
     45{
     46    var post_type = jQuery('#post_type').val();
     47    var post_tax = jQuery('#post_tax_'+ post_type).val();
     48   
     49    jQuery('.type-tax').hide();
     50   
     51    jQuery('#typetax-'+ post_tax).fadeIn();
     52   
     53    pixel_cluster_code();
     54}
    1455
    1556function pixel_cluster_change_type()
     
    2970    var mode = pixel_cluster_variable( jQuery('#mode').val() );
    3071    var number = pixel_cluster_variable( jQuery('#number').val() );
     72    var post_type = pixel_cluster_variable( jQuery('#post_type').val() );
    3173    var tag_id = '';
     74    var taxonomy = '';
    3275   
    3376    if(type == 1)
     
    3982        tag_id = pixel_cluster_variable( jQuery('#tag').val() );
    4083    }
     84    else if(type == 3)
     85    {
     86        var taxonomy = pixel_cluster_variable( jQuery('#post_tax_'+ post_type).val() );
     87        tag_id = pixel_cluster_variable( jQuery('#term_'+ taxonomy).val() );
     88    }
     89    else if(type == 4)
     90    {
     91        tag_id = pixel_cluster_variable( jQuery('#product_cat').val() );
     92    }
     93    else if(type == 5)
     94    {
     95        tag_id = pixel_cluster_variable( jQuery('#product_tag').val() );
     96    }
    4197   
    42     jQuery('#code-generate').html('[cluster type="'+ type +'" tag_id="'+ tag_id +'"  modo="'+ mode +'" numero="'+ number +'"]');   
     98    jQuery('#code-generate').html('[cluster type="'+ type +'" tag_id="'+ tag_id +'"  modo="'+ mode +'" numero="'+ number +'"');
     99   
     100    if(type == 3)
     101    {
     102        jQuery('#code-generate').append(' post_type="'+ post_type +'" taxonomy="'+ taxonomy +'"');
     103    }
     104   
     105    jQuery('#code-generate').append(']');   
    43106}
    44107
    45108function pixel_cluster_variable(data)
    46109{
    47     if(data === null)
     110    if(data === null || data === undefined)
    48111    {
    49112        data = '';
  • pixel-clusters/trunk/pixel-clusters.php

    r3405223 r3416780  
    11<?php
    22/**
     3 * Pixel Clusters - A modern plugin for creating post clusters
    34 *
    45 * @since             1.0.0
     
    89 * Plugin Name:       Pixel Clusters
    910 * Plugin URI:        https://mireunion.com/
    10  * Description:       A small plugin for create clusters of posts.
    11  * Version:           1.0.8
     11 * Description:       A modern plugin for creating beautiful clusters of posts, categories, tags, and WooCommerce products with responsive design. Includes Gutenberg block support.
     12 * Version:           2.1.0
    1213 * Author:            Mex Avila
    1314 * Author URI:        https://datakun.com/
     
    1617 * Text Domain:       pixel-cluster
    1718 * Domain Path:       /languages
    18  * Network:           true
     19 * Network:           true
     20 * Requires at least: 6.4
     21 * Tested up to:      6.9
     22 * Requires PHP:      8.0
    1923 *
    2024 * This program is free software: you can redistribute it and/or modify
     
    3135 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    3236 */
    33 
     37 
    3438if ( ! defined( 'ABSPATH' ) ) {
    35     die( 'Invalid request.' );
     39    exit; // Exit if accessed directly
    3640}
    3741
    38 define( 'CLUSTER_PLUGIN_PATH', plugin_dir_path( __FILE__ ) );
    39 define( 'CLUSTER_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
    40 
    41 function pixel_cluster( $atts )
    42 {
    43     global $post;
    44    
    45     $html = '<aside class="cluster">';
    46    
    47     $a = shortcode_atts( array(
    48         'type'      => 1,
    49         'tag_id'    => 1,
    50         'numero'    => 4,
    51         'modo'      => 1,
    52     ), $atts );
    53    
    54    
    55     $args = array(
    56         'posts_per_page'    => $a['numero'],       
    57         'orderby'           => 'date',
    58         'order'             => 'DESC',
    59         'post_type'         => 'post',
    60         'post_status'       => 'publish',   
    61         'post__not_in'      => array($post->ID)
    62     );
    63    
    64     if($a['type'] == 1)
    65     {
    66         $args['cat'] = $a['tag_id'];
    67     }
    68     else
    69     {
    70         $args['tag_id'] = $a['tag_id'];
    71     }
    72    
    73     $posts = get_posts($args); 
    74    
    75     foreach($posts as $data)
    76     {   
    77         switch($a['modo'])
    78         {
    79             case 1:
    80            
    81             $html .= '<article class="category">
    82                 <div class="images">';
    83                     if ( has_post_thumbnail($data->ID) ) :
    84                         $html .= '<a href="'. esc_url( get_the_permalink($data->ID) ) .'" title="'. esc_attr( get_the_title($data->ID) ) .'">
    85                             '. get_the_post_thumbnail($data->ID, 'thumbnail') .'
    86                         </a>';
    87                     endif;
    88                     $html .= '&nbsp;
    89                 </div>
    90                 <div class="articles">           
    91                     <div class="h2"><a href="'. esc_url( get_the_permalink($data->ID) ) .'">'. esc_html( get_the_title($data->ID) ) .'</a></div>                 
    92                     '. wp_kses_post( get_the_excerpt($data->ID) ) .'       
    93                 </div>
    94             </article>';                break;
    95                
    96             case 2:
    97            
    98                 $html .= '<article class="column3">
    99                     <a href="'. esc_url( get_the_permalink($data->ID) ) .'" title="'. esc_attr( get_the_title($data->ID) ) .'"><div class="images" style="background-image: url('. esc_url( get_the_post_thumbnail_url($data->ID, 'medium') ) .');">&nbsp;</div></a>
    100                     <div class="articles">           
    101                         <div class="h2"><a href="'. esc_url( get_the_permalink($data->ID) ) .'">'. esc_html( get_the_title($data->ID) ) .'</a></div>       
    102                     </div>
    103                 </article>';
    104            
    105                 break;
    106            
    107             case 3:
    108            
    109                 $html .= '<li><a href="'. esc_url( get_the_permalink($data->ID) ) .'">'. esc_html( get_the_title($data->ID) ) .'</a></li>';
    110            
    111                 break;
    112         }
    113     }
    114     wp_reset_postdata();
    115    
    116     $html .= '</aside>';
    117 
    118     return $html;
     42// Plugin Constants
     43define( 'PIXEL_CLUSTER_VERSION', '2.1.0' );
     44define( 'PIXEL_CLUSTER_PATH', plugin_dir_path( __FILE__ ) );
     45define( 'PIXEL_CLUSTER_URL', plugin_dir_url( __FILE__ ) );
     46define( 'PIXEL_CLUSTER_BASENAME', plugin_basename( __FILE__ ) );
     47
     48// Legacy constants for backward compatibility
     49define( 'CLUSTER_PLUGIN_PATH', PIXEL_CLUSTER_PATH );
     50define( 'CLUSTER_PLUGIN_URL', PIXEL_CLUSTER_URL );
     51
     52// Include Gutenberg Block
     53require_once PIXEL_CLUSTER_PATH . 'blocks/cluster-block.php';
     54
     55/**
     56 * Main Pixel Cluster Class
     57 */
     58final class Pixel_Cluster {
     59   
     60    /**
     61     * Instance
     62     * @var Pixel_Cluster
     63     */
     64    private static $instance = null;
     65   
     66    /**
     67     * Get instance
     68     */
     69    public static function get_instance() {
     70        if ( null === self::$instance ) {
     71            self::$instance = new self();
     72        }
     73        return self::$instance;
     74    }
     75   
     76    /**
     77     * Constructor
     78     */
     79    private function __construct() {
     80        $this->init_hooks();
     81    }
     82   
     83    /**
     84     * Initialize hooks
     85     */
     86    private function init_hooks() {
     87        // Admin menu
     88        add_action( 'admin_menu', array( $this, 'add_admin_menu' ) );
     89       
     90        // Enqueue scripts
     91        add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
     92        add_action( 'wp_enqueue_scripts', array( $this, 'frontend_enqueue_scripts' ) );
     93       
     94        // Shortcode
     95        add_shortcode( 'cluster', array( $this, 'render_shortcode' ) );
     96       
     97        // AJAX handlers
     98        add_action( 'wp_ajax_pixel_cluster_preview', array( $this, 'ajax_preview' ) );
     99        add_action( 'wp_ajax_pixel_cluster_get_taxonomies', array( $this, 'ajax_get_taxonomies' ) );
     100        add_action( 'wp_ajax_pixel_cluster_get_terms', array( $this, 'ajax_get_terms' ) );
     101        add_action( 'wp_ajax_woocommerce_ajax_add_to_cart', array( $this, 'ajax_add_to_cart' ) );
     102        add_action( 'wp_ajax_nopriv_woocommerce_ajax_add_to_cart', array( $this, 'ajax_add_to_cart' ) );
     103       
     104        // Plugin action links
     105        add_filter( 'plugin_action_links_' . PIXEL_CLUSTER_BASENAME, array( $this, 'plugin_action_links' ) );
     106    }
     107   
     108    /**
     109     * Add admin menu
     110     */
     111    public function add_admin_menu() {
     112        add_menu_page(
     113            __( 'Pixel Clusters', 'pixel-cluster' ),
     114            __( 'Clusters', 'pixel-cluster' ),
     115            'manage_options',
     116            'pixel-clusters',
     117            array( $this, 'render_admin_page' ),
     118            'dashicons-grid-view',
     119            30
     120        );
     121    }
     122   
     123    /**
     124     * Admin enqueue scripts
     125     */
     126    public function admin_enqueue_scripts( $hook ) {
     127        if ( 'toplevel_page_pixel-clusters' !== $hook ) {
     128            return;
     129        }
     130       
     131        // CSS
     132        wp_enqueue_style(
     133            'pixel-clusters-admin',
     134            PIXEL_CLUSTER_URL . 'css/admin.css',
     135            array(),
     136            PIXEL_CLUSTER_VERSION
     137        );
     138       
     139        // JS
     140        wp_enqueue_script(
     141            'pixel-clusters-admin',
     142            PIXEL_CLUSTER_URL . 'js/admin.js',
     143            array( 'jquery' ),
     144            PIXEL_CLUSTER_VERSION,
     145            true
     146        );
     147       
     148        // Localize script
     149        wp_localize_script( 'pixel-clusters-admin', 'pixelClustersAdmin', array(
     150            'ajaxUrl' => admin_url( 'admin-ajax.php' ),
     151            'nonce'   => wp_create_nonce( 'pixel_cluster_nonce' ),
     152            'i18n'    => array(
     153                'copied'        => __( 'Shortcode copied!', 'pixel-cluster' ),
     154                'copyError'     => __( 'Error copying shortcode', 'pixel-cluster' ),
     155                'previewError'  => __( 'Error loading preview', 'pixel-cluster' ),
     156            )
     157        ) );
     158    }
     159   
     160    /**
     161     * Frontend enqueue scripts
     162     */
     163    public function frontend_enqueue_scripts() {
     164        wp_enqueue_style(
     165            'pixel-clusters',
     166            PIXEL_CLUSTER_URL . 'css/pixel-clusters.css',
     167            array(),
     168            PIXEL_CLUSTER_VERSION
     169        );
     170       
     171        // Only load JS if WooCommerce is active
     172        if ( class_exists( 'WooCommerce' ) ) {
     173            wp_enqueue_script(
     174                'pixel-clusters',
     175                PIXEL_CLUSTER_URL . 'js/cluster.js',
     176                array( 'jquery' ),
     177                PIXEL_CLUSTER_VERSION,
     178                true
     179            );
     180           
     181            wp_localize_script( 'pixel-clusters', 'pixelClustersFront', array(
     182                'ajaxUrl' => admin_url( 'admin-ajax.php' ),
     183                'nonce'   => wp_create_nonce( 'pixel_cluster_front_nonce' ),
     184                'cartUrl' => function_exists( 'wc_get_cart_url' ) ? wc_get_cart_url() : '/cart/',
     185            ) );
     186        }
     187    }
     188   
     189    /**
     190     * Render shortcode
     191     */
     192    public function render_shortcode( $atts ) {
     193        global $post;
     194       
     195        $atts = shortcode_atts( array(
     196            'type'      => 1,
     197            'tag_id'    => '',
     198            'numero'    => 4,
     199            'modo'      => 1,
     200            'post_type' => 'post',
     201            'taxonomy'  => ''
     202        ), $atts, 'cluster' );
     203       
     204        $args = array(
     205            'posts_per_page' => absint( $atts['numero'] ),
     206            'orderby'        => 'date',
     207            'order'          => 'DESC',
     208            'post_status'    => 'publish',
     209            'post__not_in'   => $post ? array( $post->ID ) : array()
     210        );
     211       
     212        // Build query based on type
     213        switch ( absint( $atts['type'] ) ) {
     214            case 1: // Categories
     215                if ( ! empty( $atts['tag_id'] ) ) {
     216                    $args['cat'] = absint( $atts['tag_id'] );
     217                }
     218                $args['post_type'] = sanitize_text_field( $atts['post_type'] );
     219                break;
     220               
     221            case 2: // Tags
     222                if ( ! empty( $atts['tag_id'] ) ) {
     223                    $args['tag_id'] = absint( $atts['tag_id'] );
     224                }
     225                $args['post_type'] = sanitize_text_field( $atts['post_type'] );
     226                break;
     227               
     228            case 3: // Custom Post Type
     229                $args['post_type'] = sanitize_text_field( $atts['post_type'] );
     230                if ( ! empty( $atts['tag_id'] ) && ! empty( $atts['taxonomy'] ) ) {
     231                    $args['tax_query'] = array(
     232                        array(
     233                            'taxonomy' => sanitize_text_field( $atts['taxonomy'] ),
     234                            'field'    => 'term_id',
     235                            'terms'    => absint( $atts['tag_id'] ),
     236                        ),
     237                    );
     238                }
     239                break;
     240               
     241            case 4: // WooCommerce Categories
     242                $args['post_type'] = 'product';
     243                if ( ! empty( $atts['tag_id'] ) ) {
     244                    $args['tax_query'] = array(
     245                        array(
     246                            'taxonomy' => 'product_cat',
     247                            'field'    => 'term_id',
     248                            'terms'    => absint( $atts['tag_id'] ),
     249                        ),
     250                    );
     251                }
     252                break;
     253               
     254            case 5: // WooCommerce Tags
     255                $args['post_type'] = 'product';
     256                if ( ! empty( $atts['tag_id'] ) ) {
     257                    $args['tax_query'] = array(
     258                        array(
     259                            'taxonomy' => 'product_tag',
     260                            'field'    => 'term_id',
     261                            'terms'    => absint( $atts['tag_id'] ),
     262                        ),
     263                    );
     264                }
     265                break;
     266        }
     267       
     268        $posts = get_posts( $args );
     269       
     270        if ( empty( $posts ) ) {
     271            return '';
     272        }
     273       
     274        // Generate HTML based on mode
     275        return $this->generate_cluster_html( $posts, absint( $atts['modo'] ) );
     276    }
     277   
     278    /**
     279     * Generate cluster HTML
     280     */
     281    private function generate_cluster_html( $posts, $mode ) {
     282        // Use both old and new classes for backward compatibility
     283        $classes = 'cluster pc-cluster pc-cluster--mode-' . $mode;
     284        if ( 4 === $mode ) {
     285            $classes .= ' flex pc-cluster--flex';
     286        }
     287       
     288        $html = '<aside class="' . esc_attr( $classes ) . '">';
     289       
     290        foreach ( $posts as $post_item ) {
     291            $html .= $this->generate_item_html( $post_item, $mode );
     292        }
     293       
     294        wp_reset_postdata();
     295        $html .= '</aside>';
     296       
     297        return $html;
     298    }
     299   
     300    /**
     301     * Generate single item HTML
     302     */
     303    private function generate_item_html( $post_item, $mode ) {
     304        $permalink = get_the_permalink( $post_item->ID );
     305        $title = get_the_title( $post_item->ID );
     306        $thumbnail_url = get_the_post_thumbnail_url( $post_item->ID, 'medium' );
     307       
     308        switch ( $mode ) {
     309            case 1: // Image, title and excerpt
     310                return sprintf(
     311                    '<article class="category pc-item pc-item--full">
     312                        <div class="images pc-item__image">
     313                            %s
     314                        </div>
     315                        <div class="articles pc-item__content">
     316                            <div class="h2 pc-item__title">
     317                                <a href="%s">%s</a>
     318                            </div>
     319                            <div class="pc-item__excerpt">%s</div>
     320                        </div>
     321                    </article>',
     322                    has_post_thumbnail( $post_item->ID )
     323                        ? sprintf(
     324                            '<a href="%s" title="%s">%s</a>',
     325                            esc_url( $permalink ),
     326                            esc_attr( $title ),
     327                            get_the_post_thumbnail( $post_item->ID, 'thumbnail', array( 'class' => 'pc-item__img' ) )
     328                        )
     329                        : '<span class="pc-item__placeholder"></span>',
     330                    esc_url( $permalink ),
     331                    esc_html( $title ),
     332                    wp_kses_post( get_the_excerpt( $post_item->ID ) )
     333                );
     334               
     335            case 2: // Image and title only
     336                return sprintf(
     337                    '<article class="column3 pc-item pc-item--card">
     338                        <a href="%s" title="%s" class="pc-item__image-link">
     339                            <div class="images pc-item__image-bg" style="background-image: url(%s);"></div>
     340                        </a>
     341                        <div class="articles pc-item__content">
     342                            <div class="h2 pc-item__title">
     343                                <a href="%s">%s</a>
     344                            </div>
     345                        </div>
     346                    </article>',
     347                    esc_url( $permalink ),
     348                    esc_attr( $title ),
     349                    esc_url( $thumbnail_url ?: '' ),
     350                    esc_url( $permalink ),
     351                    esc_html( $title )
     352                );
     353               
     354            case 3: // List only
     355                return sprintf(
     356                    '<li class="pc-item pc-item--list">
     357                        <a href="%s">%s</a>
     358                    </li>',
     359                    esc_url( $permalink ),
     360                    esc_html( $title )
     361                );
     362               
     363            case 4: // Products
     364                if ( ! class_exists( 'WooCommerce' ) ) {
     365                    return '';
     366                }
     367               
     368                $product = wc_get_product( $post_item->ID );
     369                if ( ! $product || 'instock' !== $product->get_stock_status() ) {
     370                    return '';
     371                }
     372               
     373                return sprintf(
     374                    '<article class="columns pc-item pc-item--product">
     375                        <a href="%s" title="%s" class="pc-item__image-link">
     376                            <div class="images pc-item__image-bg" style="background-image: url(%s);"></div>
     377                        </a>
     378                        <div class="articles pc-item__content">
     379                            <div class="h2 pc-item__title">
     380                                <a href="%s">%s</a>
     381                            </div>
     382                            <div class="price pc-item__price">%s</div>
     383                            <div class="buttons pc-item__buttons">
     384                                <a href="%s" class="pc-btn pc-btn--view">%s</a>
     385                                <button type="button" class="carrito pc-btn pc-btn--cart" data-product-id="%d" onclick="pixelClusterBuyProduct(%d)">%s</button>
     386                            </div>
     387                        </div>
     388                    </article>',
     389                    esc_url( $permalink ),
     390                    esc_attr( $title ),
     391                    esc_url( $thumbnail_url ?: '' ),
     392                    esc_url( $permalink ),
     393                    esc_html( $title ),
     394                    $this->format_price( $product->get_price() ),
     395                    esc_url( $permalink ),
     396                    esc_html__( 'View more', 'pixel-cluster' ),
     397                    absint( $post_item->ID ),
     398                    absint( $post_item->ID ),
     399                    esc_html__( 'Add to cart', 'pixel-cluster' )
     400                );
     401               
     402            default:
     403                return '';
     404        }
     405    }
     406   
     407    /**
     408     * Format price
     409     */
     410    private function format_price( $price ) {
     411        if ( function_exists( 'wc_price' ) ) {
     412            return wc_price( $price );
     413        }
     414       
     415        return '<span class="pc-price">' . esc_html( number_format( (float) $price, 2 ) ) . '</span>';
     416    }
     417   
     418    /**
     419     * Get post types
     420     */
     421    public function get_post_types() {
     422        $post_types = get_post_types( array(
     423            'public'   => true,
     424            '_builtin' => false
     425        ), 'objects' );
     426       
     427        $exclude = array( 'product', 'attachment' );
     428        $result = array(
     429            'types'      => array(),
     430            'taxonomies' => array()
     431        );
     432       
     433        foreach ( $post_types as $post_type ) {
     434            if ( in_array( $post_type->name, $exclude, true ) ) {
     435                continue;
     436            }
     437           
     438            $result['types'][ $post_type->name ] = $post_type->label;
     439            $result['taxonomies'][ $post_type->name ] = array();
     440           
     441            $taxonomies = get_object_taxonomies( $post_type->name, 'objects' );
     442            foreach ( $taxonomies as $taxonomy ) {
     443                $result['taxonomies'][ $post_type->name ][ $taxonomy->name ] = $taxonomy->label;
     444            }
     445        }
     446       
     447        return $result;
     448    }
     449   
     450    /**
     451     * Render admin page
     452     */
     453    public function render_admin_page() {
     454        require_once PIXEL_CLUSTER_PATH . 'content/pixel-html.php';
     455    }
     456   
     457    /**
     458     * AJAX: Preview shortcode
     459     */
     460    public function ajax_preview() {
     461        check_ajax_referer( 'pixel_cluster_nonce', 'nonce' );
     462       
     463        if ( ! current_user_can( 'manage_options' ) ) {
     464            wp_send_json_error( array( 'message' => __( 'Unauthorized', 'pixel-cluster' ) ) );
     465        }
     466       
     467        $shortcode = isset( $_POST['shortcode'] ) ? sanitize_text_field( wp_unslash( $_POST['shortcode'] ) ) : '';
     468       
     469        if ( empty( $shortcode ) ) {
     470            wp_send_json_error( array( 'message' => __( 'Invalid shortcode', 'pixel-cluster' ) ) );
     471        }
     472       
     473        $html = do_shortcode( $shortcode );
     474       
     475        if ( empty( $html ) ) {
     476            wp_send_json_error( array( 'message' => __( 'No posts found', 'pixel-cluster' ) ) );
     477        }
     478       
     479        wp_send_json_success( array( 'html' => $html ) );
     480    }
     481   
     482    /**
     483     * AJAX: Get taxonomies
     484     */
     485    public function ajax_get_taxonomies() {
     486        check_ajax_referer( 'pixel_cluster_nonce', 'nonce' );
     487       
     488        $post_type = isset( $_POST['post_type'] ) ? sanitize_text_field( wp_unslash( $_POST['post_type'] ) ) : '';
     489       
     490        if ( empty( $post_type ) ) {
     491            wp_send_json_error();
     492        }
     493       
     494        $taxonomies = get_object_taxonomies( $post_type, 'objects' );
     495        $result = array();
     496       
     497        foreach ( $taxonomies as $taxonomy ) {
     498            $result[] = array(
     499                'name'  => $taxonomy->name,
     500                'label' => $taxonomy->label
     501            );
     502        }
     503       
     504        wp_send_json_success( $result );
     505    }
     506   
     507    /**
     508     * AJAX: Get terms
     509     */
     510    public function ajax_get_terms() {
     511        check_ajax_referer( 'pixel_cluster_nonce', 'nonce' );
     512       
     513        $taxonomy = isset( $_POST['taxonomy'] ) ? sanitize_text_field( wp_unslash( $_POST['taxonomy'] ) ) : '';
     514       
     515        if ( empty( $taxonomy ) ) {
     516            wp_send_json_error();
     517        }
     518       
     519        $terms = get_terms( array(
     520            'taxonomy'   => $taxonomy,
     521            'hide_empty' => true,
     522            'orderby'    => 'name',
     523            'order'      => 'ASC'
     524        ) );
     525       
     526        $result = array();
     527       
     528        if ( ! is_wp_error( $terms ) ) {
     529            foreach ( $terms as $term ) {
     530                $result[] = array(
     531                    'id'   => $term->term_id,
     532                    'name' => $term->name
     533                );
     534            }
     535        }
     536       
     537        wp_send_json_success( $result );
     538    }
     539   
     540    /**
     541     * AJAX: Add to cart
     542     */
     543    public function ajax_add_to_cart() {
     544        if ( ! class_exists( 'WooCommerce' ) ) {
     545            echo 'FAIL';
     546            wp_die();
     547        }
     548       
     549        $product_id = isset( $_REQUEST['product_id'] ) ? absint( $_REQUEST['product_id'] ) : 0;
     550        $quantity = isset( $_REQUEST['quantity'] ) ? wc_stock_amount( wp_unslash( $_REQUEST['quantity'] ) ) : 1;
     551        $variation_id = isset( $_REQUEST['variation_id'] ) ? absint( $_REQUEST['variation_id'] ) : 0;
     552       
     553        $product_status = get_post_status( $product_id );
     554        $passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity );
     555       
     556        if ( $passed_validation && WC()->cart->add_to_cart( $product_id, $quantity, $variation_id ) && 'publish' === $product_status ) {
     557            do_action( 'woocommerce_ajax_added_to_cart', $product_id );
     558           
     559            if ( 'yes' === get_option( 'woocommerce_cart_redirect_after_add' ) ) {
     560                wc_add_to_cart_message( array( $product_id => $quantity ), true );
     561            }
     562           
     563            WC_AJAX::get_refreshed_fragments();
     564            echo 'GOCART';
     565        } else {
     566            // Check if product already in cart
     567            $in_cart = false;
     568            foreach ( WC()->cart->get_cart() as $cart_item ) {
     569                if ( $cart_item['product_id'] === $product_id ) {
     570                    $in_cart = true;
     571                    break;
     572                }
     573            }
     574           
     575            echo $in_cart ? 'DUPLICATE' : 'FAIL';
     576        }
     577       
     578        wp_die();
     579    }
     580   
     581    /**
     582     * Plugin action links
     583     */
     584    public function plugin_action_links( $links ) {
     585        $settings_link = sprintf(
     586            '<a href="%s">%s</a>',
     587            admin_url( 'admin.php?page=pixel-clusters' ),
     588            __( 'Settings', 'pixel-cluster' )
     589        );
     590       
     591        array_unshift( $links, $settings_link );
     592       
     593        return $links;
     594    }
    119595}
    120 add_shortcode( 'cluster', 'pixel_cluster' );
    121 
    122 
    123 function pixel_cluster_admin()
    124 {
    125     add_menu_page( 'Clusters', 'Clusters', 'manage_options', 'clusters', 'safetya_cluster_page' );
     596
     597// Legacy function for backward compatibility
     598function pixel_cluster( $atts ) {
     599    return Pixel_Cluster::get_instance()->render_shortcode( $atts );
    126600}
    127 add_action( 'admin_menu', 'pixel_cluster_admin' );
    128 
    129 function pixel_cluster_admin_enqueue($hook)
    130 {       
    131     if($hook != 'toplevel_page_clusters') {
    132             return;
    133     }
    134    
    135     wp_enqueue_script( 'pixel-clusters-js', CLUSTER_PLUGIN_URL . 'js/cluster.js', array ( 'jquery' ), 1.0, true);
     601
     602// Legacy function for post types
     603function pixel_cluster_post_type() {
     604    $data = Pixel_Cluster::get_instance()->get_post_types();
     605    return array(
     606        'name_type'       => array_keys( $data['types'] ),
     607        'name_taxonomies' => $data['taxonomies']
     608    );
    136609}
    137 add_action( 'admin_enqueue_scripts', 'pixel_cluster_admin_enqueue' );
    138 
    139 function pixel_cluster_init()
    140 {
    141     if ( ! is_admin() )
    142     {
    143         wp_enqueue_style( 'pixel-clusters', CLUSTER_PLUGIN_URL .'css/pixel-clusters.css' );
    144     }
    145     else
    146     {
    147         wp_enqueue_script( 'pixel-clusters-js', CLUSTER_PLUGIN_URL . 'js/cluster.js', array ( 'jquery' ), 1.0, true);
    148     }
     610
     611// Legacy money function
     612function pixel_cluster_money( $money, $print = true ) {
     613    if ( function_exists( 'wc_price' ) ) {
     614        $formatted = wc_price( $money );
     615    } else {
     616        $formatted = '<nobr>' . number_format( (float) $money, 2 ) . '</nobr>';
     617    }
     618   
     619    if ( $print ) {
     620        echo wp_kses_post( $formatted );
     621    } else {
     622        return $formatted;
     623    }
    149624}
    150 add_action('wp_enqueue_scripts', 'pixel_cluster_init');
    151 
    152 function safetya_cluster_page()
    153 {
    154     require_once( CLUSTER_PLUGIN_PATH . 'content/pixel-html.php' );
    155 }
     625
     626// Initialize plugin
     627Pixel_Cluster::get_instance();
  • pixel-clusters/trunk/readme.txt

    r3405223 r3416780  
    1 === Pixel Cluster ===
     1=== Pixel Clusters ===
    22Contributors: kakaroto84
    3 Tags: cluster, posts, categories, tags, pixel
    43Donate link: https://www.paypal.me/datakun
     4Tags: cluster, posts, categories, tags, shortcode, responsive, woocommerce, custom post type
    55Requires at least: 6.4
    66Tested up to: 6.9
    77Requires PHP: 8.0
    8 Stable tag: trunk
     8Stable tag: 2.1.0
    99License: GPLv3 or later
    1010License URI: http://www.gnu.org/licenses/gpl-3.0.html
    1111
    12 A small plugin for create clusters of posts.
     12Create beautiful, responsive post clusters with shortcodes or Gutenberg blocks. Display posts, categories, tags, custom post types, and WooCommerce products in elegant layouts.
    1313
    1414== Description ==
    15 You can create your clusters an easy way.
    16 
    17 1. Go to Clusters
    18 2. Select the options
    19 3. Copy the shortcode
    20 4. Paste in your post/page
     15
     16**Pixel Clusters** is a powerful yet simple WordPress plugin that allows you to create beautiful clusters of posts, categories, tags, and products using shortcodes.
     17
     18= 🚀 Key Features =
     19
     20* **Gutenberg Block**: Native block editor support with live preview
     21* **4 Display Modes**: Choose from full articles, card layouts, simple lists, or product grids
     22* **Multiple Content Sources**: Works with categories, tags, custom post types, and WooCommerce
     23* **Fully Responsive**: Looks great on all devices - desktop, tablet, and mobile
     24* **Modern Admin Interface**: Clean, AJAX-powered admin panel with live preview
     25* **WooCommerce Integration**: Display products with prices and add-to-cart buttons
     26* **Custom Post Type Support**: Works with any registered custom post type and taxonomy
     27* **Easy to Use**: Simple shortcode generator or Gutenberg block - no coding required
     28* **Performance Optimized**: Lightweight and fast-loading
     29* **Accessibility Ready**: Full keyboard navigation and screen reader support
     30
     31= 🎨 Display Modes =
     32
     331. **Full Article** - Image, title, and excerpt in a clean layout
     342. **Cards** - Beautiful card grid with images and titles
     353. **List** - Simple, elegant list of post titles
     364. **Products** - WooCommerce products with prices and cart buttons
     37
     38= 📝 How to Use =
     39
     40**Using Gutenberg Block (Recommended):**
     41
     421. Open the Block Editor in any post or page
     432. Click the + button to add a block
     443. Search for "Pixel Cluster" or find it in the Widgets category
     454. Configure settings in the block sidebar
     465. See live preview directly in the editor
     47
     48**Using Shortcode Generator:**
     49
     501. Go to **Clusters** in your WordPress admin menu
     512. Select your preferred display mode
     523. Choose the content type (categories, tags, custom post type, etc.)
     534. Set the number of posts to display
     545. Copy the generated shortcode
     556. Paste it in any post, page, or widget
     56
     57= 💡 Example Shortcodes =
     58
     59Display 4 posts from a category:
     60`[cluster type="1" tag_id="5" modo="1" numero="4"]`
     61
     62Display products from WooCommerce:
     63`[cluster type="4" modo="4" numero="6"]`
     64
     65Display custom post type:
     66`[cluster type="3" post_type="portfolio" taxonomy="portfolio_cat" tag_id="10" modo="2" numero="3"]`
     67
     68= 🔧 Shortcode Parameters =
     69
     70* **type** - Content source: 1=Categories, 2=Tags, 3=Custom Post Type, 4=WooCommerce Categories, 5=WooCommerce Tags
     71* **tag_id** - The term ID to filter by (optional)
     72* **modo** - Display mode: 1=Full, 2=Cards, 3=List, 4=Products
     73* **numero** - Number of posts to display (default: 4)
     74* **post_type** - Custom post type slug (for type=3)
     75* **taxonomy** - Custom taxonomy slug (for type=3)
    2176
    2277== Installation ==
    23 Install Pixel Cluster like you would install any other WordPress plugin.
    24 
    25 Dashboard Method:
    26 
    27 1. Login to your WordPress admin and go to Plugins -> Add New
    28 2. Type \"Pixel Cluster\" in the search bar and select this plugin
    29 3. Click \"Install\", and then \"Activate Plugin\"
    30 
    31 
    32 Upload Method:
    33 
    34 1. Unzip the plugin and upload the \"pixel-cluster\" folder to your \'wp-content/plugins\' directory
    35 2. Activate the plugin through the Plugins menu in WordPress
     78
     79= Automatic Installation =
     80
     811. Go to **Plugins → Add New** in your WordPress admin
     822. Search for "Pixel Clusters"
     833. Click **Install Now** and then **Activate**
     84
     85= Manual Installation =
     86
     871. Download the plugin ZIP file
     882. Go to **Plugins → Add New → Upload Plugin**
     893. Choose the ZIP file and click **Install Now**
     904. Activate the plugin
     91
     92= FTP Installation =
     93
     941. Download and unzip the plugin
     952. Upload the `pixel-clusters` folder to `/wp-content/plugins/`
     963. Activate through the **Plugins** menu in WordPress
    3697
    3798== Frequently Asked Questions ==
    38 = Works in customs posts? =
    39  
    40 Yes, it works!
     99
     100= Does it work with custom post types? =
     101
     102Yes! Pixel Clusters fully supports custom post types and their taxonomies. Select "Custom Post Type" in the generator and choose your post type and taxonomy.
     103
     104= Is it compatible with WooCommerce? =
     105
     106Absolutely! Display your WooCommerce products with prices and add-to-cart buttons. Select mode 4 (Products) for the best WooCommerce experience.
     107
     108= Can I customize the styling? =
     109
     110Yes, all elements have CSS classes that you can target in your theme's stylesheet or the WordPress Customizer.
     111
     112= Is it responsive? =
     113
     114Yes! All display modes are fully responsive and look great on mobile devices, tablets, and desktops.
     115
     116= Does it support Gutenberg? =
     117
     118Yes! Pixel Clusters includes a native Gutenberg block with full support for:
     119* Live preview in the editor
     120* All display modes and content types
     121* Block alignment (wide and full width)
     122* Server-side rendering for accurate previews
     123
     124Simply search for "Pixel Cluster" in the block inserter to add it to your content.
     125
     126= Is it translation ready? =
     127
     128Yes! The plugin is fully translatable and includes a POT file for translations.
    41129
    42130== Screenshots ==
    43 1. Configure the shortcode
    44 2. Copy / paste in post / page
    45 3. How works
     131
     1321. Modern admin interface with shortcode generator
     1332. Live preview feature
     1343. Documentation tab with parameter reference
     1354. Card layout display mode
     1365. Product grid with WooCommerce integration
     1376. Mobile responsive design
    46138
    47139== Changelog ==
     140
     141= 2.1.0 =
     142**Gutenberg Block Support**
     143
     144* **New**: Native Gutenberg block for the block editor
     145* **New**: Live preview in the block editor
     146* **New**: Block sidebar controls for all settings
     147* **New**: Support for wide and full width alignments
     148* **New**: Server-side rendering for accurate previews
     149* **New**: Dynamic category/tag/taxonomy loading
     150* **Improved**: Better integration with WordPress 6.9 block editor
     151
     152= 2.0.0 =
     153**Major Update - Complete Redesign**
     154
     155* **New**: Modern, AJAX-powered admin interface
     156* **New**: Live shortcode preview feature
     157* **New**: Tab-based navigation (Generator / Documentation)
     158* **New**: Toast notifications for user feedback
     159* **New**: CSS variables for easy customization
     160* **New**: Grid-based responsive layouts
     161* **New**: Improved accessibility (WCAG 2.1 compliant)
     162* **New**: Support for WordPress admin color schemes
     163* **Improved**: Complete CSS rewrite with modern standards
     164* **Improved**: Better WooCommerce integration
     165* **Improved**: Enhanced security with proper escaping and nonces
     166* **Improved**: Code refactored using OOP principles
     167* **Improved**: Performance optimizations
     168* **Improved**: Mobile responsive design
     169* **Fixed**: Various bug fixes and improvements
     170* **Compatibility**: Tested with WordPress 6.9
     171* **Compatibility**: Tested with PHP 8.0, 8.1, 8.2, and 8.3
     172
    48173= 1.0.8 =
    49174* Tested and confirmed compatibility with WordPress 6.9
    50175* Updated Clipboard API to modern standard
    51176* Improved output escaping for security
    52 = 1.0 =
    53 * Initial release
    54 = 1.0.1 =
    55 * Add copy button
     177
     178= 1.0.7 =
     179* Tested in last WP Version
     180
     181= 1.0.6.1 =
     182* Change required version of PHP
     183
     184= 1.0.6 =
     185* Tested in last WP Version
     186
     187= 1.0.5 =
     188* Tested in last WP Version
     189
     190= 1.0.4 =
     191* Tested in last WP Version
     192
     193= 1.0.3 =
     194* Tested in last WP Version
     195
    56196= 1.0.2 =
    57197* Tested new version of WP
    58 = 1.0.3 =
    59 * Tested in last WP Version
    60 = 1.0.4 =
    61 * Tested in last WP Version
    62 = 1.0.5 =
    63 * Tested in last WP Version
    64 = 1.0.6 =
    65 * Tested in last WP Version
    66 = 1.0.6.1 =
    67 * Change required version of PHP
    68 = 1.0.7 =
    69 * Tested in last WP Version
     198
     199= 1.0.1 =
     200* Added copy button for shortcode
     201
     202= 1.0.0 =
     203* Initial release
     204
     205== Upgrade Notice ==
     206
     207= 2.1.0 =
     208New Gutenberg block support! Create post clusters directly in the block editor with live preview. Fully backward compatible with existing shortcodes.
     209
     210= 2.0.0 =
     211Major update with completely redesigned admin interface, live preview, improved responsive design, and enhanced WooCommerce integration. Recommended update for all users.
     212
     213== Privacy Policy ==
     214
     215Pixel Clusters does not collect, store, or share any personal data. The plugin does not use cookies, tracking, or external services.
Note: See TracChangeset for help on using the changeset viewer.