Plugin Directory

Changeset 3464012


Ignore:
Timestamp:
02/18/2026 07:29:57 AM (6 weeks ago)
Author:
rstheme2017
Message:

Version 2.0.2 Released

Location:
fancy-post-grid
Files:
87 added
8 edited

Legend:

Unmodified
Added
Removed
  • fancy-post-grid/trunk/assets/css/shortcode-metabox.css

    r3456923 r3464012  
    1 .fpg-metabox{background:#fff}.fpg-metabox .fpg-metabox-tabs{display:flex;gap:6px;border-bottom:1px solid #e2e4e7;margin-bottom:16px}.fpg-metabox .fpg-metabox-tabs .fpg-metabox-tab{position:relative;padding:10px 18px;background:rgba(0,0,0,0);border:none;cursor:pointer;font-weight:600;color:#555;transition:all .25s ease}.fpg-metabox .fpg-metabox-tabs .fpg-metabox-tab::after{content:"";position:absolute;left:50%;bottom:-1px;width:0;height:3px;background:#2271b1;transition:all .25s ease;transform:translateX(-50%)}.fpg-metabox .fpg-metabox-tabs .fpg-metabox-tab:hover{color:#2271b1}.fpg-metabox .fpg-metabox-tabs .fpg-metabox-tab.is-active{color:#2271b1}.fpg-metabox .fpg-metabox-tabs .fpg-metabox-tab.is-active::after{width:100%}.fpg-metabox .fpg-metabox-tab-panel{display:none;animation:fpgFadeIn .25s ease}@keyframes fpgFadeIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.fpg-metabox .fpg-metabox-field{display:flex;align-items:center;justify-content:space-between;gap:50px}.fpg-metabox .fpg-metabox-field:not(:last-child){margin-bottom:20px}.fpg-metabox .fpg-metabox-field .fpg-field-info{width:35%}.fpg-metabox .fpg-metabox-field .fpg-field-info label{display:block;font-size:15px;font-weight:500;color:#131313}.fpg-metabox .fpg-metabox-field .fpg-field-info p{margin:5px 0 0;font-size:15px;line-height:1.5}.fpg-metabox .fpg-metabox-field .fpg-field-wrap{width:50%}.fpg-metabox .fpg-metabox-field .fpg-field-wrap select{min-width:250px}
     1.fpg-metabox{background:#fff}.fpg-metabox .fpg-metabox-tabs{display:flex;gap:6px;border-bottom:1px solid #e2e4e7;margin-bottom:16px}.fpg-metabox .fpg-metabox-tabs .fpg-metabox-tab{position:relative;padding:10px 18px;background:rgba(0,0,0,0);border:none;cursor:pointer;font-weight:600;color:#555;transition:all .25s ease}.fpg-metabox .fpg-metabox-tabs .fpg-metabox-tab::after{content:"";position:absolute;left:50%;bottom:-1px;width:0;height:3px;background:#2271b1;transition:all .25s ease;transform:translateX(-50%)}.fpg-metabox .fpg-metabox-tabs .fpg-metabox-tab:hover{color:#2271b1}.fpg-metabox .fpg-metabox-tabs .fpg-metabox-tab.is-active{color:#2271b1}.fpg-metabox .fpg-metabox-tabs .fpg-metabox-tab.is-active::after{width:100%}.fpg-metabox .fpg-metabox-tab-panel{display:none;animation:fpgFadeIn .25s ease}@keyframes fpgFadeIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.fpg-metabox .fpg-metabox-field{display:flex;align-items:center;justify-content:space-between;gap:50px}.fpg-metabox .fpg-metabox-field:not(:last-child){margin-bottom:20px}.fpg-metabox .fpg-metabox-field .fpg-field-info{width:35%}.fpg-metabox .fpg-metabox-field .fpg-field-info h4{display:block;font-size:15px;font-weight:500;color:#131313;margin:0}.fpg-metabox .fpg-metabox-field .fpg-field-info p{margin:5px 0 0;font-size:15px;line-height:1.5}.fpg-metabox .fpg-metabox-field .fpg-field-wrap{width:50%}.fpg-metabox .fpg-metabox-field .fpg-field-wrap select{min-width:250px}.fpg-metabox .fpg-metabox-field .fpg-switcher{position:relative;display:inline-block;width:44px;height:22px}.fpg-metabox .fpg-metabox-field .fpg-switcher input{opacity:0;width:0;height:0}.fpg-metabox .fpg-metabox-field .fpg-switcher input:checked+.fpg-switcher-label{background-color:#2271b1}.fpg-metabox .fpg-metabox-field .fpg-switcher input:checked+.fpg-switcher-label::before{transform:translateX(22px)}.fpg-metabox .fpg-metabox-field .fpg-switcher input:focus+.fpg-switcher-label{box-shadow:0 0 0 2px rgba(34,113,177,.2)}.fpg-metabox .fpg-metabox-field .fpg-switcher .fpg-switcher-label{position:absolute;inset:0;cursor:pointer;background-color:#ccc;transition:.3s;border-radius:22px}.fpg-metabox .fpg-metabox-field .fpg-switcher .fpg-switcher-label::before{content:"";position:absolute;height:18px;width:18px;left:2px;bottom:2px;background-color:#fff;transition:.3s;border-radius:50%}.fpg-metabox .fpg-metabox-field .fpg-switcher-label+.fpg-switcher-label{position:static;margin-left:8px;vertical-align:middle;cursor:default;background:none}.fpg-metabox .fpg-metabox-field .fpg-switcher-label+.fpg-switcher-label::before{display:none}
  • fancy-post-grid/trunk/assets/js/shortcode-metabox.js

    r3456923 r3464012  
    1 jQuery(function ($) {
     1jQuery(($) => {
     2    'use strict';
    23
    3     const $tabs   = $('.fpg-metabox-tab');
    4     const $panels = $('.fpg-metabox-tab-panel');
     4    const FPGMetabox = {
     5        init() {
     6            this.initTabs();
     7            this.initSelect2();
     8            this.initConditionalFields();
     9        },
    510
    6     $tabs.on('click', function () {
    7         const tab = $(this).data('tab');
     11        initTabs() {
     12            const $tabs = $('.fpg-metabox-tab');
     13            const $panels = $('.fpg-metabox-tab-panel');
    814
    9         $tabs.removeClass('is-active');
    10         $(this).addClass('is-active');
     15            $tabs.on('click', function () {
     16                const tab = $(this).data('tab');
    1117
    12         $panels.hide();
    13         $('#fpg-metabox-tab-' + tab).fadeIn(150);
     18                $tabs.removeClass('is-active');
     19                $(this).addClass('is-active');
     20
     21                $panels.hide();
     22                $(`#fpg-metabox-tab-${tab}`).fadeIn(150);
     23            });
     24
     25            $tabs.first().trigger('click');
     26        },
     27
     28        initSelect2() {
     29            // Initialize regular select2 (non-AJAX)
     30            $('.fpg-select2:not([data-ajax="true"])').select2({
     31                width: '100%',
     32                placeholder: 'Select options...',
     33            });
     34
     35            // Initialize AJAX-enabled select2
     36            $('.fpg-select2[data-ajax="true"]').each((index, element) => {
     37                const $select = $(element);
     38                const type = $select.attr('data-type');
     39
     40                if (!type) {
     41                    console.warn('Select2 AJAX: No data-type attribute found', $select);
     42                    return;
     43                }
     44
     45                // Store pre-selected options before initializing Select2
     46                const preSelectedData = [];
     47                $select.find('option').each(function () {
     48                    if ($(this).is(':selected')) {
     49                        preSelectedData.push({
     50                            id: $(this).val(),
     51                            text: $(this).text()
     52                        });
     53                    }
     54                });
     55
     56                $select.select2({
     57                    width: '100%',
     58                    placeholder: 'Search and select...',
     59                    minimumInputLength: 0,
     60                    allowClear: true,
     61                    ajax: {
     62                        url: fpgMetabox.ajaxUrl,
     63                        dataType: 'json',
     64                        delay: 250,
     65                        data: (params) => ({
     66                            action: 'fpg_load_select_options',
     67                            nonce: fpgMetabox.nonce,
     68                            type,
     69                            search: params.term || '',
     70                            page: params.page || 1
     71                        }),
     72                        processResults: (response, params) => {
     73                            params.page = params.page || 1;
     74
     75                            if (response.success) {
     76                                return {
     77                                    results: response.data.results,
     78                                    pagination: {
     79                                        more: response.data.pagination.more
     80                                    }
     81                                };
     82                            }
     83
     84                            return {results: []};
     85                        },
     86                        cache: true
     87                    },
     88                    templateResult: this.formatOption,
     89                    templateSelection: this.formatSelection,
     90                    escapeMarkup: (markup) => markup
     91                });
     92
     93                // Re-add pre-selected options programmatically
     94                if (preSelectedData.length > 0) {
     95                    preSelectedData.forEach((item) => {
     96                        // Check if option already exists
     97                        if ($select.find(`option[value='${item.id}']`).length === 0) {
     98                            const newOption = new Option(item.text, item.id, true, true);
     99                            $select.append(newOption);
     100                        } else {
     101                            $select.find(`option[value='${item.id}']`).prop('selected', true);
     102                        }
     103                    });
     104                    $select.trigger('change');
     105                }
     106            });
     107        },
     108
     109        initConditionalFields() {
     110            // Check all fields on load
     111            this.applyConditions();
     112
     113            // Listen for changes
     114            $(document).on('change', 'select, input', () => {
     115                this.applyConditions();
     116            });
     117        },
     118
     119        applyConditions() {
     120            $('.fpg-metabox-field').each(function () {
     121                const condition = $(this).data('condition');
     122                if (!condition?.key) return;
     123
     124                const $inputs = $(`[name="fpg_meta[${condition.key}]"]`);
     125
     126                let value;
     127
     128                const $checkbox = $inputs.filter('[type="checkbox"]');
     129
     130                if ($checkbox.length) {
     131                    value = $checkbox.is(':checked');
     132                } else {
     133                    value = $inputs.val();
     134                }
     135
     136                $(this).toggle(value === condition.value);
     137            });
     138        },
     139
     140        formatOption(option) {
     141            if (option.loading) {
     142                return option.text;
     143            }
     144
     145            return $('<span></span>').text(option.text);
     146        },
     147
     148        formatSelection(option) {
     149            return option.text || option.id;
     150        }
     151    };
     152
     153    $(function () {
     154        FPGMetabox.init();
    14155    });
    15 
    16     $tabs.first().trigger('click');
    17 
    18     $('.fpg-select2').select2({
    19         width: '100%'
    20     });
    21 
    22     function applyConditions() {
    23         $('.fpg-metabox-field').each(function () {
    24             const condition = $(this).data('condition');
    25             if (!condition || !condition.key) return;
    26 
    27             const $target = $('[name="fpg_meta[' + condition.key + ']"]');
    28             const value   = $target.val();
    29 
    30             $(this).toggle(value === condition.value);
    31         });
    32     }
    33 
    34     $(document).on('change', 'select, input', applyConditions);
    35     applyConditions();
    36156});
  • fancy-post-grid/trunk/fancy-post-grid.php

    r3456954 r3464012  
    66 * Author: RSTheme
    77 * Author URI: https://rstheme.com/
    8  * Version: 2.0.1
     8 * Version: 2.0.2
    99 * Text Domain: fancy-post-grid
    1010 * Domain Path: /languages
  • fancy-post-grid/trunk/includes/shortcode/class-shortcode-cpt.php

    r3456923 r3464012  
    2424            2
    2525        );
     26
     27        add_action( 'add_meta_boxes', [ $this, 'register_shortcode_panel' ] );
    2628    }
    2729
     
    5254            'hierarchical'        => false,
    5355            'capability_type'     => 'post',
    54             'supports'            => [ 'title', 'author', ],
     56            'supports'            => [ 'title', 'author' ],
    5557        ] );
    5658    }
    5759
    5860    public function replace_cpt_enter_title( $input, $post ) {
    59 
    6061        if ( isset( $post->post_type ) && $post->post_type === $this->post_type ) {
    6162            return esc_html__( 'Enter Shortcode Name', 'fancy-post-grid' );
     
    6566    }
    6667
    67     public function add_custom_column( $columns ) {
    68         unset( $columns[ 'author' ] );
     68    private function get_shortcode( $post_id ): string {
     69        return sprintf( '[fpg_shortcode id="%d"]', absint( $post_id ) );
     70    }
     71
     72    public function add_custom_column( $columns ): array {
    6973        unset( $columns[ 'date' ] );
    7074
    71         $columns[ 'shortcode' ] = __( 'Shortcode', 'fancy-post-grid' );
    72         $columns[ 'author' ]    = __( 'Author', 'fancy-post-grid' );
    73         $columns[ 'date' ]      = __( 'Date', 'fancy-post-grid' );
     75        $new_columns = [];
    7476
    75         return $columns;
     77        foreach ( $columns as $key => $label ) {
     78            $new_columns[ $key ] = $label;
     79
     80            if ( 'title' === $key ) {
     81                $new_columns[ 'shortcode' ] = __( 'Shortcode', 'fancy-post-grid' );
     82            }
     83        }
     84
     85        $new_columns[ 'date' ] = __( 'Date', 'fancy-post-grid' );
     86
     87        return $new_columns;
    7688    }
    7789
     
    8193        }
    8294
    83         $shortcode = sprintf(
    84             '[fpg_shortcode id="%d"]',
    85             $post_id
    86         );
     95        $shortcode = $this->get_shortcode( $post_id );
    8796        ?>
    8897        <input
     
    96105        <?php
    97106    }
     107
     108    public function register_shortcode_panel() {
     109        add_meta_box(
     110            'fpg_shortcode_display_panel',
     111            __( 'Shortcode', 'fancy-post-grid' ),
     112            [ $this, 'render_shortcode_panel' ],
     113            $this->post_type,
     114            'side',
     115            'high'
     116        );
     117    }
     118
     119    public function render_shortcode_panel( $post ) {
     120        if ( 'auto-draft' === $post->post_status ) {
     121            echo '<p>' . esc_html__( 'Save this shortcode to generate its ID.', 'fancy-post-grid' ) . '</p>';
     122
     123            return;
     124        }
     125
     126        $shortcode = $this->get_shortcode( $post->ID );
     127        ?>
     128
     129        <p style="margin-bottom:6px;">
     130            <?php esc_html_e( 'Copy this shortcode and paste it anywhere:', 'fancy-post-grid' ); ?>
     131        </p>
     132
     133        <input
     134                type="text"
     135                class="widefat fpg-shortcode-input"
     136                value="<?php echo esc_attr( $shortcode ); ?>"
     137                readonly
     138                onclick="this.select();"
     139                style="cursor:pointer;margin-bottom:6px;"
     140        />
     141
     142        <button
     143                type="button"
     144                class="button button-small"
     145                onclick="navigator.clipboard.writeText('<?php echo esc_js( $shortcode ); ?>')"
     146        >
     147            <?php esc_html_e( 'Copy', 'fancy-post-grid' ); ?>
     148        </button>
     149        <?php
     150    }
    98151}
  • fancy-post-grid/trunk/includes/shortcode/class-shortcode-metabox.php

    r3456923 r3464012  
    1111    private string $post_type = 'fpg_shortcode';
    1212
    13     private string $nonce_action = 'fpg_shortcode_meta_nonce';
    14 
    15     private string $nonce_name = 'fpg_shortcode_meta_nonce_field';
    16 
    1713    private const NONCE_ACTION = 'fpg_shortcode_meta_nonce';
    1814
     
    2218
    2319    public function __construct() {
    24         add_action( 'init', function () {
    25             $this->meta_fields = $this->get_meta_fields_config();
    26         } );
     20        add_action( 'init', [ $this, 'configure_meta_fields' ] );
    2721
    2822        add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
    2923        add_action( 'add_meta_boxes', [ $this, 'add_metabox' ] );
    3024        add_action( 'save_post', [ $this, 'save_metabox' ], 10, 2 );
    31     }
    32 
    33     private function get_meta_fields_config(): array {
     25
     26        // AJAX handlers for dynamic options
     27        add_action( 'wp_ajax_fpg_load_select_options', [ $this, 'ajax_load_select_options' ] );
     28    }
     29
     30    public function configure_meta_fields(): void {
    3431        $config = [
    3532            'layout'        => [
    36                 'label'  => __( 'Layout', 'fancy-post-grid' ),
    37                 'icon'   => 'dashicons-grid-view',
     33                'label'  => __( 'Layout Settings', 'fancy-post-grid' ),
    3834                'fields' => [
    3935                    [
     
    4945                    ],
    5046                    [
    51                         'id'          => 'card_layout',
    52                         'type'        => 'select',
    53                         'label'       => __( 'Card Layout', 'fancy-post-grid' ),
    54                         'description' => __( 'Choose the card design layout.', 'fancy-post-grid' ),
    55                         'options'     => [
    56                             'one'   => __( 'Layout One', 'fancy-post-grid' ),
    57                             'two'   => __( 'Layout Two', 'fancy-post-grid' ),
    58                             'three' => __( 'Layout Three', 'fancy-post-grid' ),
     47                        'id'          => 'col_lg',
     48                        'type'        => 'number',
     49                        'label'       => __( 'Column Large', 'fancy-post-grid' ),
     50                        'description' => __( 'Column for large device.', 'fancy-post-grid' ),
     51                        'default'     => 3,
     52                    ],
     53                    [
     54                        'id'          => 'col_md',
     55                        'type'        => 'number',
     56                        'label'       => __( 'Column Medium', 'fancy-post-grid' ),
     57                        'description' => __( 'Column for medium device.', 'fancy-post-grid' ),
     58                        'default'     => 2,
     59                    ],
     60                    [
     61                        'id'          => 'col_sm',
     62                        'type'        => 'number',
     63                        'label'       => __( 'Column Small', 'fancy-post-grid' ),
     64                        'description' => __( 'Column for small device.', 'fancy-post-grid' ),
     65                        'default'     => 1,
     66                    ],
     67                    [
     68                        'id'          => 'col_gap',
     69                        'type'        => 'number',
     70                        'label'       => __( 'Column Gap', 'fancy-post-grid' ),
     71                        'description' => __( 'Space between per column.', 'fancy-post-grid' ),
     72                        'default'     => 30,
     73                    ],
     74                    [
     75                        'id'          => 'row_gap',
     76                        'type'        => 'number',
     77                        'label'       => __( 'Row Gap', 'fancy-post-grid' ),
     78                        'description' => __( 'Space between per row.', 'fancy-post-grid' ),
     79                        'default'     => 30,
     80                        'condition'   => [ 'key' => 'layout_type', 'value' => 'grid' ],
     81                    ]
     82                ],
     83            ],
     84            'card_setting'  => [
     85                'label'  => __( 'Card Settings', 'fancy-post-grid' ),
     86                'fields' => [
     87                    [
     88                        'id'      => 'title_tag',
     89                        'type'    => 'select',
     90                        'label'   => __( 'Title Tag', 'fancy-post-grid' ),
     91                        'default' => 'h4',
     92                        'options' => [
     93                            'h1'   => 'H1',
     94                            'h2'   => 'H2',
     95                            'h3'   => 'H3',
     96                            'h4'   => 'H4',
     97                            'h5'   => 'H5',
     98                            'h6'   => 'H6',
     99                            'p'    => 'P',
     100                            'div'  => 'DIV',
     101                            'span' => 'SPAN',
    59102                        ],
    60                         'default'     => 'one',
    61                     ],
    62                     [
    63                         'id'          => 'pagination',
    64                         'type'        => 'switcher',
    65                         'label'       => __( 'Enable Pagination', 'fancy-post-grid' ),
    66                         'description' => __( 'Show pagination for posts.', 'fancy-post-grid' ),
    67                         'default'     => false,
    68                     ],
    69                 ],
     103                    ],
     104                    [
     105                        'id'      => 'show_thumb',
     106                        'type'    => 'switcher',
     107                        'label'   => __( 'Show Thumbnail', 'fancy-post-grid' ),
     108                        'default' => true,
     109                    ],
     110                    [
     111                        'id'        => 'thumb_size',
     112                        'type'      => 'select',
     113                        'label'     => __( 'Thumbnail Size', 'fancy-post-grid' ),
     114                        'default'   => 'large',
     115                        'options'   => $this->get_image_sizes_options(),
     116                        'condition' => [ 'key' => 'show_thumb', 'value' => true ],
     117                    ],
     118                    [
     119                        'id'      => 'show_excerpt',
     120                        'type'    => 'switcher',
     121                        'label'   => __( 'Show Excerpt', 'fancy-post-grid' ),
     122                        'default' => false,
     123                    ],
     124                    [
     125                        'id'        => 'excerpt_length',
     126                        'type'      => 'number',
     127                        'label'     => __( 'Excerpt Length', 'fancy-post-grid' ),
     128                        'default'   => 30,
     129                        'condition' => [ 'key' => 'show_excerpt', 'value' => true ],
     130                    ],
     131                    [
     132                        'id'      => 'read_more',
     133                        'type'    => 'switcher',
     134                        'label'   => __( 'Read More', 'fancy-post-grid' ),
     135                        'default' => false,
     136                    ],
     137                    [
     138                        'id'        => 'read_more_text',
     139                        'type'      => 'text',
     140                        'label'     => __( 'Read More Text', 'fancy-post-grid' ),
     141                        'default'   => __( 'Read More', 'fancy-post-grid' ),
     142                        'condition' => [ 'key' => 'read_more', 'value' => true ],
     143                    ],
     144                    [
     145                        'id'      => 'show_category',
     146                        'type'    => 'switcher',
     147                        'label'   => __( 'Show Category', 'fancy-post-grid' ),
     148                        'default' => true,
     149                    ]
     150                ]
    70151            ],
    71152            'query_builder' => [
    72153                'label'  => __( 'Query Builder', 'fancy-post-grid' ),
    73                 'icon'   => 'dashicons-filter',
    74154                'fields' => [
    75155                    [
     
    122202                        'default'     => 0,
    123203                        'min'         => 0,
     204                    ],
     205                    [
     206                        'id'      => 'order',
     207                        'type'    => 'select',
     208                        'label'   => __( 'Order', 'fancy-post-grid' ),
     209                        'default' => 'DESC',
     210                        'options' => [
     211                            'ASC'  => __( 'Ascending', 'fancy-post-grid' ),
     212                            'DESC' => __( 'Descending', 'fancy-post-grid' ),
     213                        ]
     214                    ],
     215                    [
     216                        'id'      => 'order_by',
     217                        'type'    => 'select',
     218                        'label'   => __( 'Order By', 'fancy-post-grid' ),
     219                        'default' => 'date',
     220                        'options' => [
     221                            'none'          => __( 'None', 'fancy-post-grid' ),
     222                            'ID'            => __( 'ID', 'fancy-post-grid' ),
     223                            'title'         => __( 'Title', 'fancy-post-grid' ),
     224                            'author'        => __( 'Author', 'fancy-post-grid' ),
     225                            'name'          => __( 'Name', 'fancy-post-grid' ),
     226                            'date'          => __( 'Date', 'fancy-post-grid' ),
     227                            'modified'      => __( 'Modified', 'fancy-post-grid' ),
     228                            'rand'          => __( 'Random', 'fancy-post-grid' ),
     229                            'comment_count' => __( 'Comment Count', 'fancy-post-grid' ),
     230                            'menu_order'    => __( 'Menu Order', 'fancy-post-grid' ),
     231                        ]
    124232                    ],
    125233                    [
     
    199307        ];
    200308
    201         return apply_filters( 'fpg_meta_fields_config', $config );
     309        apply_filters( 'fpg_meta_fields_config', $config );
     310
     311        $this->meta_fields = $config;
    202312    }
    203313
     
    213323        wp_enqueue_style( 'fpg-metabox', FPG_ASSETS . 'css/shortcode-metabox.css', [], FPG_VERSION );
    214324        wp_enqueue_script( 'fpg-metabox', FPG_ASSETS . 'js/shortcode-metabox.js', [ 'jquery', 'select2' ], FPG_VERSION, true );
     325
     326        // Localize script for AJAX
     327        wp_localize_script( 'fpg-metabox', 'fpgMetabox', [
     328            'ajaxUrl' => admin_url( 'admin-ajax.php' ),
     329            'nonce'   => wp_create_nonce( 'fpg_metabox_ajax' ),
     330        ] );
    215331    }
    216332
     
    316432    }
    317433
    318     private function render_field( array $field, array $saved ): void {
     434    private function render_field( $field, $saved ): void {
    319435        $id    = $field[ 'id' ];
    320436        $type  = $field[ 'type' ];
     
    325441
    326442        echo '<div class="fpg-field-info">';
    327         echo '<label>' . esc_html( $field[ 'label' ] ) . '</label>';
     443        echo '<h4>' . esc_html( $field[ 'label' ] ) . '</h4>';
    328444        if ( ! empty( $field[ 'description' ] ) ) {
    329445            echo '<p>' . esc_html( $field[ 'description' ] ) . '</p>';
     
    344460                break;
    345461
    346             default:
    347                 do_action( 'fpg_render_custom_field', $field, $value );
     462            case 'text':
     463                $this->render_text_field( $field, $value );
    348464                break;
    349465        }
     
    352468    }
    353469
    354     private function render_select_field( array $field, $value ): void {
     470    private function render_select_field( $field, $value ): void {
    355471        $id       = $field[ 'id' ];
    356472        $options  = $field[ 'options' ];
    357473        $multiple = ! empty( $field[ 'multiple' ] );
    358474
    359         printf(
    360             '<select name="fpg_meta[%s]%s" class="fpg-select %s" %s>',
    361             esc_attr( $id ),
    362             $multiple ? '[]' : '',
    363             $multiple ? 'fpg-select2' : '',
    364             $multiple ? 'multiple' : ''
    365         );
     475        // Check if this is a dynamic field that needs AJAX
     476        $is_dynamic = in_array( $options, [ 'posts', 'categories', 'tags' ], true );
     477        $use_ajax   = $is_dynamic && $multiple;
     478
     479        $select_attrs = [
     480            sprintf( 'name="fpg_meta[%s]%s"', esc_attr( $id ), $multiple ? '[]' : '' ),
     481            sprintf( 'class="fpg-select %s"', $multiple ? 'fpg-select2' : '' ),
     482        ];
     483
     484        if ( $multiple ) {
     485            $select_attrs[] = 'multiple="multiple"';
     486        }
     487
     488        if ( $use_ajax ) {
     489            $select_attrs[] = 'data-ajax="true"';
     490            $select_attrs[] = sprintf( 'data-type="%s"', esc_attr( $options ) );
     491        }
     492
     493        printf( '<select %s>', implode( ' ', $select_attrs ) );
    366494
    367495        if ( is_array( $options ) ) {
     496            // Static options
    368497            foreach ( $options as $key => $label ) {
    369                 $this->render_option(
    370                     $key,
    371                     $label,
    372                     $value,
    373                     $multiple
    374                 );
     498                $this->render_option( $key, $label, $value, $multiple );
     499            }
     500        } elseif ( $use_ajax ) {
     501            // AJAX-enabled field: load only selected values initially
     502            if ( ! empty( $value ) ) {
     503                $selected_items = $this->get_selected_items( $options, (array) $value );
     504                foreach ( $selected_items as $item ) {
     505                    $this->render_dynamic_option( $item, $options, $value, $multiple );
     506                }
    375507            }
    376508        } else {
     509            // Non-AJAX dynamic field: load all options (backward compatibility)
    377510            foreach ( $this->get_dynamic_options( $options ) as $item ) {
    378                 switch ( $options ) {
    379                     case 'posts':
    380                         $key   = $item->ID;
    381                         $label = $item->post_title;
    382                         break;
    383 
    384                     case 'categories':
    385                     case 'tags':
    386                         $key   = $item->term_id;
    387                         $label = $item->name;
    388                         break;
    389 
    390                     default:
    391                         continue 2;
    392                 }
    393 
    394                 $this->render_option(
    395                     $key,
    396                     $label,
    397                     $value,
    398                     $multiple
    399                 );
     511                $this->render_dynamic_option( $item, $options, $value, $multiple );
    400512            }
    401513        }
     
    404516    }
    405517
    406     private function render_number_field( array $field, $value ): void {
     518    private function render_number_field( $field, $value ): void {
     519        $attrs = [];
     520
     521        if ( isset( $field[ 'min' ] ) ) {
     522            $attrs[] = 'min="' . esc_attr( $field[ 'min' ] ) . '"';
     523        }
     524
     525        if ( isset( $field[ 'max' ] ) ) {
     526            $attrs[] = 'max="' . esc_attr( $field[ 'max' ] ) . '"';
     527        }
     528
     529        if ( isset( $field[ 'step' ] ) ) {
     530            $attrs[] = 'step="' . esc_attr( $field[ 'step' ] ) . '"';
     531        }
     532
    407533        printf(
    408             '<input type="number" name="fpg_meta[%s]" value="%s" min="%s" max="%s">',
     534            '<input type="number"
     535                name="fpg_meta[%s]"
     536                value="%s"
     537                %s
     538            >',
    409539            esc_attr( $field[ 'id' ] ),
    410540            esc_attr( $value ),
    411             esc_attr( $field[ 'min' ] ) ?? '',
    412             esc_attr( $field[ 'max' ] ) ?? ''
     541            implode( ' ', $attrs )
    413542        );
    414543    }
    415544
     545    private function render_text_field( $field, $value ): void {
     546        printf(
     547            '<input type="text"
     548                name="fpg_meta[%s]"
     549                value="%s"
     550                placeholder="%s"
     551            >',
     552            esc_attr( $field[ 'id' ] ),
     553            esc_attr( $value ),
     554            esc_attr( $field[ 'placeholder' ] ?? '' ),
     555        );
     556    }
     557
    416558    private function render_switcher_field( array $field, $value ): void {
    417         printf(
    418             '<input type="hidden" name="fpg_meta[%s]" value="0">
    419             <input type="checkbox" name="fpg_meta[%s]" value="1" %s>',
    420             esc_attr( $field[ 'id' ] ),
    421             esc_attr( $field[ 'id' ] ),
    422             checked( $value, true, false )
    423         );
    424     }
    425 
    426     private function get_dynamic_options( string $type ): array {
     559        $id      = esc_attr( $field[ 'id' ] );
     560        $checked = checked( $value, true, false );
     561        ?>
     562        <label class="fpg-switcher">
     563            <input type="hidden" name="fpg_meta[<?php echo $id; ?>]" value="0">
     564
     565            <input
     566                    type="checkbox"
     567                    name="fpg_meta[<?php echo $id; ?>]"
     568                    value="1"
     569                <?php echo $checked; ?>
     570            >
     571
     572            <span class="fpg-switcher-label"></span>
     573        </label>
     574        <?php
     575    }
     576
     577    private function get_dynamic_options( $type, $limit = - 1, $search = '' ): array {
     578        switch ( $type ) {
     579            case 'posts':
     580                $args = [
     581                    'post_type'      => 'post',
     582                    'posts_per_page' => $limit > 0 ? $limit : 50,
     583                    'post_status'    => 'publish',
     584                    'orderby'        => 'title',
     585                    'order'          => 'ASC',
     586                ];
     587
     588                if ( ! empty( $search ) ) {
     589                    $args[ 's' ] = $search;
     590                }
     591
     592                return get_posts( $args );
     593
     594            case 'categories':
     595                $args = [
     596                    'taxonomy'   => 'category',
     597                    'hide_empty' => false,
     598                    'orderby'    => 'name',
     599                    'order'      => 'ASC',
     600                ];
     601
     602                if ( $limit > 0 ) {
     603                    $args[ 'number' ] = $limit;
     604                }
     605
     606                if ( ! empty( $search ) ) {
     607                    $args[ 'search' ] = $search;
     608                }
     609
     610                return get_terms( $args );
     611
     612            case 'tags':
     613                $args = [
     614                    'taxonomy'   => 'post_tag',
     615                    'hide_empty' => false,
     616                    'orderby'    => 'name',
     617                    'order'      => 'ASC',
     618                ];
     619
     620                if ( $limit > 0 ) {
     621                    $args[ 'number' ] = $limit;
     622                }
     623
     624                if ( ! empty( $search ) ) {
     625                    $args[ 'search' ] = $search;
     626                }
     627
     628                return get_terms( $args );
     629        }
     630
     631        return [];
     632    }
     633
     634    private function get_selected_items( $type, $ids ): array {
     635        if ( empty( $ids ) ) {
     636            return [];
     637        }
     638
    427639        switch ( $type ) {
    428640            case 'posts':
    429641                return get_posts( [
    430642                    'post_type'      => 'post',
    431                     'posts_per_page' => 50,
     643                    'posts_per_page' => - 1,
    432644                    'post_status'    => 'publish',
     645                    'post__in'       => array_map( 'intval', $ids ),
     646                    'orderby'        => 'post__in',
    433647                ] );
    434648
     
    437651                    'taxonomy'   => 'category',
    438652                    'hide_empty' => false,
     653                    'include'    => array_map( 'intval', $ids ),
    439654                ] );
    440655
     
    443658                    'taxonomy'   => 'post_tag',
    444659                    'hide_empty' => false,
     660                    'include'    => array_map( 'intval', $ids ),
    445661                ] );
    446662        }
     
    449665    }
    450666
    451     private function render_option( $key, string $label, $value, bool $multiple ): void {
     667    private function render_dynamic_option( $item, $type, $value, $multiple ): void {
     668        switch ( $type ) {
     669            case 'posts':
     670                $key   = $item->ID;
     671                $label = $item->post_title;
     672                break;
     673
     674            case 'categories':
     675            case 'tags':
     676                $key   = $item->term_id;
     677                $label = $item->name;
     678                break;
     679
     680            default:
     681                return;
     682        }
     683
     684        $this->render_option( $key, $label, $value, $multiple );
     685    }
     686
     687    private function render_option( $key, $label, $value, $multiple ): void {
    452688        printf(
    453689            '<option value="%s" %s>%s</option>',
     
    463699        );
    464700    }
     701
     702    public function ajax_load_select_options(): void {
     703        check_ajax_referer( 'fpg_metabox_ajax', 'nonce' );
     704
     705        $type   = isset( $_GET[ 'type' ] ) ? sanitize_text_field( wp_unslash( $_GET[ 'type' ] ) ) : '';
     706        $search = isset( $_GET[ 'search' ] ) ? sanitize_text_field( wp_unslash( $_GET[ 'search' ] ) ) : '';
     707        $page   = isset( $_GET[ 'page' ] ) ? absint( $_GET[ 'page' ] ) : 1;
     708
     709        if ( ! in_array( $type, [ 'posts', 'categories', 'tags' ], true ) ) {
     710            wp_send_json_error( [ 'message' => 'Invalid type' ] );
     711        }
     712
     713        $per_page = 10;
     714        $offset   = ( $page - 1 ) * $per_page;
     715
     716        $items   = $this->get_dynamic_options( $type, $per_page, $search );
     717        $results = [];
     718
     719        foreach ( $items as $item ) {
     720            switch ( $type ) {
     721                case 'posts':
     722                    $results[] = [
     723                        'id'   => $item->ID,
     724                        'text' => $item->post_title,
     725                    ];
     726                    break;
     727
     728                case 'categories':
     729                case 'tags':
     730                    $results[] = [
     731                        'id'   => $item->term_id,
     732                        'text' => $item->name,
     733                    ];
     734                    break;
     735            }
     736        }
     737
     738        wp_send_json_success( [
     739            'results'    => $results,
     740            'pagination' => [
     741                'more' => count( $items ) === $per_page,
     742            ],
     743        ] );
     744    }
     745
     746    private function get_image_sizes_options(): array {
     747        $options = [];
     748
     749        $core_sizes = [
     750            'thumbnail'    => [
     751                'label'  => __( 'Thumbnail', 'fancy-post-grid' ),
     752                'width'  => get_option( 'thumbnail_size_w' ),
     753                'height' => get_option( 'thumbnail_size_h' ),
     754            ],
     755            'medium'       => [
     756                'label'  => __( 'Medium', 'fancy-post-grid' ),
     757                'width'  => get_option( 'medium_size_w' ),
     758                'height' => get_option( 'medium_size_h' ),
     759            ],
     760            'medium_large' => [
     761                'label'  => __( 'Medium Large', 'fancy-post-grid' ),
     762                'width'  => get_option( 'medium_large_size_w' ),
     763                'height' => get_option( 'medium_large_size_h' ), // often 0
     764            ],
     765            'large'        => [
     766                'label'  => __( 'Large', 'fancy-post-grid' ),
     767                'width'  => get_option( 'large_size_w' ),
     768                'height' => get_option( 'large_size_h' ),
     769            ],
     770        ];
     771
     772        foreach ( $core_sizes as $key => $data ) {
     773            $options[ $key ] = sprintf(
     774                '%s - %d x %d',
     775                $data[ 'label' ],
     776                $data[ 'width' ],
     777                $data[ 'height' ]
     778            );
     779        }
     780
     781        // Get all registered sizes
     782        $all_sizes    = get_intermediate_image_sizes();
     783        $custom_sizes = wp_get_additional_image_sizes();
     784
     785        foreach ( $all_sizes as $size ) {
     786            if ( isset( $core_sizes[ $size ] ) ) {
     787                continue;
     788            }
     789
     790            // Custom registered sizes
     791            if ( isset( $custom_sizes[ $size ] ) ) {
     792                $width  = $custom_sizes[ $size ][ 'width' ];
     793                $height = $custom_sizes[ $size ][ 'height' ];
     794
     795                $label = ucwords( str_replace( [ '-', '_' ], ' ', $size ) );
     796
     797                $options[ $size ] = sprintf(
     798                    '%s - %d x %d',
     799                    $label,
     800                    $width,
     801                    $height
     802                );
     803            }
     804        }
     805
     806        $options[ 'full' ] = __( 'Full', 'fancy-post-grid' );
     807
     808        return $options;
     809    }
    465810}
    466 
  • fancy-post-grid/trunk/includes/shortcode/class-shortcode-renderer.php

    r3456923 r3464012  
    1010
    1111    private string $post_type = 'fpg_shortcode';
     12
    1213    private string $shortcode = 'fpg_shortcode';
     14
    1315    private string $meta_key = '_fpg_shortcode_meta';
     16
     17    private array $current_meta = [];
    1418
    1519    public function __construct() {
    1620        add_shortcode( $this->shortcode, [ $this, 'render' ] );
     21
     22        add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_assets_if_shortcode' ] );
     23    }
     24
     25    public function enqueue_assets_if_shortcode(): void {
     26        global $post;
     27
     28        if ( isset( $post ) && has_shortcode( $post->post_content, $this->shortcode ) ) {
     29            wp_enqueue_style(
     30                'fpg-shortcode',
     31                FPG_ASSETS . 'css/shortcode-frontend.css',
     32                [ 'rs-swiper' ],
     33                FPG_VERSION
     34            );
     35
     36            wp_enqueue_script(
     37                'fpg-shortcode',
     38                FPG_ASSETS . 'js/shortcode-frontend.js',
     39                [ 'rs-swiper' ],
     40                FPG_VERSION,
     41                true
     42            );
     43        }
    1744    }
    1845
    1946    public function render( array $atts ): string {
    20 
    2147        $atts = shortcode_atts(
    2248            [
     
    3965        }
    4066
    41         $meta = get_post_meta( $post_id, $this->meta_key, true );
    42         $meta = is_array( $meta ) ? $meta : [];
    43 
    44         $query_args = $this->build_query_args( $meta );
     67        $meta               = get_post_meta( $post_id, $this->meta_key, true );
     68        $this->current_meta = is_array( $meta ) ? $meta : [];
     69
     70        $query_args    = $this->build_query_args();
     71        $style_atts    = $this->build_style_atts();
     72        $slider_config = $this->build_slider_config();
     73        $layout        = $this->get_setting( 'layout_type', 'grid' );
     74        $classes       = "fpg-shortcode-wrapper fpg-shortcode-{$post_id} fpg-layout-{$layout}";
     75
     76        if ( 'slider' === $layout ) {
     77            $classes .= ' fpg-swiper-active swiper';
     78        }
    4579
    4680        $query = new WP_Query( $query_args );
     
    4882        ob_start();
    4983        ?>
    50         <div class="fpg-grid-wrapper fpg-grid-<?php echo esc_attr( $meta[ 'layout' ][ 'layout_type' ] ?? 'grid' ); ?>" data-id="<?php echo esc_attr( $post_id ); ?>">
     84        <div
     85                class="<?php echo esc_attr( $classes ) ?>"
     86                data-id="<?php echo esc_attr( $post_id ); ?>"
     87            <?php if ( ! empty( $style_atts ) ) : ?>
     88                style="<?php echo esc_attr( $style_atts ) ?>"
     89            <?php endif; ?>
     90            <?php if ( ! empty( $slider_config ) ) : ?>
     91                data-slider="<?php echo $slider_config; ?>"
     92            <?php endif; ?>
     93        >
    5194            <?php if ( $query->have_posts() ) : ?>
    52                 <div class="fpg-grid-row">
    53                     <?php while ( $query->have_posts() ) : $query->the_post(); ?>
    54                         <div class="fpg-grid-item fpg-card-<?php echo esc_attr( $meta[ 'layout' ][ 'card_layout' ] ?? 'one' ); ?>">
    55                             <?php $this->render_card( get_the_ID(), $meta ); ?>
    56                         </div>
    57                     <?php endwhile; ?>
     95                <?php
     96                if ( 'slider' === $layout ) {
     97                    echo '<div class="swiper-wrapper">';
     98                }
     99                while ( $query->have_posts() ) : $query->the_post();
     100                    if ( 'slider' === $layout ) {
     101                        echo '<div class="swiper-slide">';
     102                    }
     103                    include FPG_INCLUDES . 'shortcode/templates/card-one.php';
     104                    if ( 'slider' === $layout ) {
     105                        echo '</div>';
     106                    }
     107                endwhile;
     108                wp_reset_postdata();
     109                if ( 'slider' === $layout ) {
     110                    echo '</div>';
     111                }
     112                ?>
     113            <?php else : ?>
     114                <div class="fpg-shortcode-np-posts">
     115                    <?php esc_html_e( 'No posts found.', 'fancy-post-grid' ); ?>
    58116                </div>
    59 
    60                 <?php if ( ! empty( $meta[ 'layout' ][ 'pagination' ] ) ) : ?>
    61                     <div class="fpg-pagination">
    62                         <?php echo wp_kses_post(
    63                             paginate_links( [
    64                                 'total'   => $query->max_num_pages,
    65                                 'current' => max( 1, get_query_var( 'paged' ) ),
    66                             ] )
    67                         ); ?>
    68                     </div>
    69                 <?php endif; ?>
    70 
    71                 <?php wp_reset_postdata(); ?>
    72             <?php else : ?>
    73                 <p><?php esc_html_e( 'No posts found.', 'fancy-post-grid' ); ?></p>
    74117            <?php endif; ?>
    75118        </div>
     
    79122    }
    80123
    81     private function build_query_args( array $meta ): array {
    82 
     124    private function build_query_args(): array {
    83125        $args = [
    84126            'post_type'      => 'post',
    85             'posts_per_page' => absint( $meta[ 'query_builder' ][ 'posts_per_page' ] ?? 10 ),
    86             'offset'         => absint( $meta[ 'query_builder' ][ 'offset' ] ?? 0 ),
     127            'posts_per_page' => absint( $this->get_setting( 'posts_per_page', 10 ) ),
     128            'offset'         => absint( $this->get_setting( 'offset', 0 ) ),
    87129            'post_status'    => 'publish',
     130            'order'          => $this->get_setting( 'order', 'DESC' ),
     131            'orderby'        => $this->get_setting( 'order_by', 'date' ),
    88132        ];
    89133
    90         if ( ( $meta[ 'query_builder' ][ 'posts_by' ] ?? 'default' ) === 'specific' && ! empty( $meta[ 'query_builder' ][ 'post_ids' ] ) ) {
    91             $args[ 'post__in' ] = array_map( 'absint', $meta[ 'query_builder' ][ 'post_ids' ] );
     134        if ( $this->get_setting( 'posts_by', 'default' ) === 'specific' && ! empty( $this->get_setting( 'post_ids', [] ) ) ) {
     135            $args[ 'post__in' ] = array_map( 'absint', $this->get_setting( 'post_ids', [] ) );
    92136            $args[ 'orderby' ]  = 'post__in';
    93137        }
    94138
    95         if ( ( $meta[ 'query_builder' ][ 'posts_by' ] ?? 'default' ) === 'default' ) {
    96             if ( ! empty( $meta[ 'query_builder' ][ 'include_category' ] ) ) {
    97                 $args[ 'category__in' ] = array_map( 'absint', $meta[ 'query_builder' ][ 'include_category' ] );
     139        if ( $this->get_setting( 'posts_by', 'default' ) === 'default' ) {
     140            if ( ! empty( $this->get_setting( 'include_category', [] ) ) ) {
     141                $args[ 'category__in' ] = array_map( 'absint', $this->get_setting( 'include_category', [] ) );
    98142            }
    99             if ( ! empty( $meta[ 'query_builder' ][ 'exclude_category' ] ) ) {
    100                 $args[ 'category__not_in' ] = array_map( 'absint', $meta[ 'query_builder' ][ 'exclude_category' ] );
     143            if ( ! empty( $this->get_setting( 'exclude_category', [] ) ) ) {
     144                $args[ 'category__not_in' ] = array_map( 'absint', $this->get_setting( 'exclude_category', [] ) );
    101145            }
    102146        }
    103147
    104         if ( ! empty( $meta[ 'query_builder' ][ 'include_tags' ] ) ) {
    105             $args[ 'tag__in' ] = array_map( 'absint', $meta[ 'query_builder' ][ 'include_tags' ] );
    106         }
    107         if ( ! empty( $meta[ 'query_builder' ][ 'exclude_tags' ] ) ) {
    108             $args[ 'tag__not_in' ] = array_map( 'absint', $meta[ 'query_builder' ][ 'exclude_tags' ] );
    109         }
    110 
    111         if ( ! empty( $meta[ 'query_builder' ][ 'exclude_posts' ] ) ) {
    112             $args[ 'post__not_in' ] = array_map( 'absint', $meta[ 'query_builder' ][ 'exclude_posts' ] );
     148        if ( ! empty( $this->get_setting( 'include_tags', [] ) ) ) {
     149            $args[ 'tag__in' ] = array_map( 'absint', $this->get_setting( 'include_tags', [] ) );
     150        }
     151        if ( ! empty( $this->get_setting( 'exclude_tags', [] ) ) ) {
     152            $args[ 'tag__not_in' ] = array_map( 'absint', $this->get_setting( 'exclude_tags', [] ) );
     153        }
     154
     155        if ( ! empty( $this->get_setting( 'exclude_posts', [] ) ) ) {
     156            $args[ 'post__not_in' ] = array_map( 'absint', $this->get_setting( 'exclude_posts', [] ) );
    113157        }
    114158
    115159        $meta_query = [];
    116         if ( ! empty( $meta[ 'query_builder' ][ 'featured_post_only' ] ) ) {
     160
     161        if ( $this->get_setting( 'featured_post_only' ) ) {
    117162            $meta_query[] = [
    118163                'key'   => '_fpg_featured_post',
     
    120165            ];
    121166        }
    122         if ( ! empty( $meta[ 'query_builder' ][ 'trending_post_only' ] ) ) {
     167
     168        if ( $this->get_setting( 'trending_post_only' ) ) {
    123169            $meta_query[] = [
    124170                'key'   => '_fpg_trending_post',
     
    126172            ];
    127173        }
    128         if ( ! empty( $meta[ 'query_builder' ][ 'editor_picks_post_only' ] ) ) {
     174
     175        if ( $this->get_setting( 'editor_picks_post_only' ) ) {
    129176            $meta_query[] = [
    130177                'key'   => '_fpg_editor_picks_post',
     
    132179            ];
    133180        }
    134         if ( ! empty( $meta[ 'query_builder' ][ 'popular_posts_only' ] ) ) {
     181
     182        if ( $this->get_setting( 'popular_posts_only' ) ) {
    135183            $meta_query[] = [
    136184                'key'     => '_fpg_post_views_count',
     
    141189            $args[ 'orderby' ]  = 'meta_value_num';
    142190            $args[ 'meta_key' ] = '_fpg_post_views_count';
    143             $args[ 'order' ]    = 'DESC';
    144         }
     191        }
     192
    145193        if ( $meta_query ) {
    146194            $args[ 'meta_query' ] = $meta_query;
     
    150198    }
    151199
    152     private function render_card( int $post_id, array $meta ): void {
    153         $layout = $meta[ 'layout' ][ 'card_layout' ] ?? 'one';
    154         ?>
    155         <div class="fpg-card-content fpg-card-<?php echo esc_attr( $layout ); ?>">
    156             <a href="<?php echo esc_url( get_permalink( $post_id ) ); ?>">
    157                 <?php if ( has_post_thumbnail( $post_id ) ) : ?>
    158                     <div class="fpg-card-thumb">
    159                         <?php echo get_the_post_thumbnail( $post_id, 'medium' ); ?>
    160                     </div>
    161                 <?php endif; ?>
    162                 <h3 class="fpg-card-title"><?php echo esc_html( get_the_title( $post_id ) ); ?></h3>
    163             </a>
    164             <div class="fpg-card-excerpt">
    165                 <?php echo wp_kses_post( wp_trim_words( get_post_field( 'post_content', $post_id ), 20 ) ); ?>
    166             </div>
    167         </div>
    168         <?php
     200    private function build_style_atts(): string {
     201        $style = '';
     202
     203        if ( 'grid' === $this->get_setting( 'layout_type', 'grid' ) ) {
     204            $style .= '--col-lg:' . $this->get_setting( 'col_lg', 3 ) . ';';
     205            $style .= '--col-md:' . $this->get_setting( 'col_md', 2 ) . ';';
     206            $style .= '--col-sm:' . $this->get_setting( 'col_sm', 1 ) . ';';
     207            $style .= '--col-gap:' . $this->get_setting( 'col_gap', 30 ) . 'px;';
     208            $style .= '--row-gap:' . $this->get_setting( 'row_gap', 30 ) . 'px;';
     209        }
     210
     211
     212        return $style;
     213    }
     214
     215    private function build_slider_config(): string {
     216        if ( 'slider' !== $this->get_setting( 'layout_type', 'grid' ) ) {
     217            return '';
     218        }
     219
     220        $config = [
     221            'colLg'  => (int) $this->get_setting( 'col_lg', 3 ),
     222            'colMd'  => (int) $this->get_setting( 'col_md', 2 ),
     223            'colSm'  => (int) $this->get_setting( 'col_sm', 1 ),
     224            'colGap' => (int) $this->get_setting( 'col_gap', 30 ),
     225        ];
     226
     227        return esc_attr( wp_json_encode( $config ) );
     228    }
     229
     230    private function get_setting( string $key, $default = null ) {
     231        return $this->current_meta[ $key ] ?? $default;
    169232    }
    170233}
  • fancy-post-grid/trunk/languages/fancy-post-grid.pot

    r3456923 r3464012  
    44"Project-Id-Version: Fancy Post Grid - Ultimate Post Grid Builder\n"
    55"Report-Msgid-Bugs-To: \n"
    6 "POT-Creation-Date: 2026-02-08 08:47+0000\n"
     6"POT-Creation-Date: 2026-02-18 06:21+0000\n"
    77"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
    88"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
     
    227227msgstr ""
    228228
    229 #: includes/shortcode/class-shortcode-cpt.php:34
     229#: includes/shortcode/class-shortcode-cpt.php:36
    230230msgid "Add New Shortcode"
    231231msgstr ""
     
    271271msgstr ""
    272272
    273 #: includes/shortcode/class-shortcode-cpt.php:33
     273#: includes/shortcode/class-shortcode-cpt.php:35
    274274msgid "All Shortcodes"
    275275msgstr ""
     
    331331msgstr ""
    332332
     333#: includes/shortcode/class-shortcode-metabox.php:211
    333334#: includes/elementor/traits/trait-query-builder.php:111
    334335msgid "Ascending"
    335336msgstr ""
    336337
    337 #: includes/shortcode/class-shortcode-cpt.php:72
     338#: includes/shortcode/class-shortcode-metabox.php:224
    338339#: includes/elementor/widgets/cards/card-style.php:21
    339340msgid "Author"
     
    562563msgstr ""
    563564
    564 #: includes/shortcode/class-shortcode-metabox.php:53
    565 msgid "Card Layout"
     565#: includes/shortcode/class-shortcode-metabox.php:85
     566msgid "Card Settings"
    566567msgstr ""
    567568
     
    618619msgstr ""
    619620
    620 #: includes/shortcode/class-shortcode-metabox.php:103
     621#: includes/shortcode/class-shortcode-metabox.php:183
    621622msgid "Choose how to select posts."
    622623msgstr ""
     
    628629msgstr ""
    629630
    630 #: includes/shortcode/class-shortcode-metabox.php:129
     631#: includes/shortcode/class-shortcode-metabox.php:237
    631632msgid "Choose specific posts to display."
    632633msgstr ""
     
    639640msgid ""
    640641"Choose the AI model responsible for generating images from text prompts."
    641 msgstr ""
    642 
    643 #: includes/shortcode/class-shortcode-metabox.php:54
    644 msgid "Choose the card design layout."
    645642msgstr ""
    646643
     
    704701msgstr ""
    705702
     703#: includes/shortcode/class-shortcode-metabox.php:50
     704msgid "Column for large device."
     705msgstr ""
     706
     707#: includes/shortcode/class-shortcode-metabox.php:57
     708msgid "Column for medium device."
     709msgstr ""
     710
     711#: includes/shortcode/class-shortcode-metabox.php:64
     712msgid "Column for small device."
     713msgstr ""
     714
     715#: includes/shortcode/class-shortcode-metabox.php:70
     716msgid "Column Gap"
     717msgstr ""
     718
     719#: includes/shortcode/class-shortcode-metabox.php:49
     720msgid "Column Large"
     721msgstr ""
     722
     723#: includes/shortcode/class-shortcode-metabox.php:56
     724msgid "Column Medium"
     725msgstr ""
     726
    706727#: includes/elementor/traits/trait-slider-controls.php:1006
    707728#: includes/elementor/widgets/class-date-time.php:300
     
    713734msgstr ""
    714735
     736#: includes/shortcode/class-shortcode-metabox.php:63
     737msgid "Column Small"
     738msgstr ""
     739
     740#: includes/shortcode/class-shortcode-metabox.php:229
     741msgid "Comment Count"
     742msgstr ""
     743
    715744#: includes/admin/view/settings.php:81
    716745msgid "Configure AI-powered content generation and image creation"
     
    740769msgstr ""
    741770
     771#: includes/shortcode/class-shortcode-cpt.php:147
     772msgid "Copy"
     773msgstr ""
     774
     775#: includes/shortcode/class-shortcode-cpt.php:130
     776msgid "Copy this shortcode and paste it anywhere:"
     777msgstr ""
     778
    742779#: includes/elementor/widgets/class-heading.php:725
    743780#: includes/elementor/widgets/class-icon-box.php:728
     
    756793#: includes/admin/view/welcome.php:42
    757794msgid "Create beautiful grid and slider layouts for your posts"
     795msgstr ""
     796
     797#. Description of the plugin
     798msgid ""
     799"Create beautiful, responsive post grids, sliders, and carousels using "
     800"Elementor, Gutenberg, or shortcodes."
    758801msgstr ""
    759802
     
    813856msgstr ""
    814857
    815 #: includes/shortcode/class-shortcode-cpt.php:73
     858#: includes/shortcode/class-shortcode-cpt.php:85
     859#: includes/shortcode/class-shortcode-metabox.php:226
    816860#: includes/elementor/widgets/class-date-time.php:161
    817861msgid "Date"
     
    873917msgstr ""
    874918
    875 #: includes/shortcode/class-shortcode-metabox.php:105
     919#: includes/shortcode/class-shortcode-metabox.php:185
    876920msgid "Default Query"
    877921msgstr ""
    878922
     923#: includes/shortcode/class-shortcode-metabox.php:212
    879924#: includes/elementor/traits/trait-query-builder.php:110
    880925msgid "Descending"
     
    9891034msgstr ""
    9901035
    991 #: includes/shortcode/class-shortcode-cpt.php:35
     1036#: includes/shortcode/class-shortcode-cpt.php:37
    9921037msgid "Edit Shortcode"
    9931038msgstr ""
    9941039
    995 #: includes/shortcode/class-shortcode-metabox.php:90
     1040#: includes/shortcode/class-shortcode-metabox.php:170
    9961041#: includes/elementor/traits/trait-query-builder.php:88
    9971042msgid "Editor Picks Only"
     
    10401085msgstr ""
    10411086
    1042 #: includes/shortcode/class-shortcode-metabox.php:65
    1043 msgid "Enable Pagination"
    1044 msgstr ""
    1045 
    10461087#: includes/elementor/widgets/class-icon-box.php:234
    10471088msgid ""
     
    10681109msgstr ""
    10691110
    1070 #: includes/shortcode/class-shortcode-cpt.php:61
     1111#: includes/shortcode/class-shortcode-cpt.php:62
    10711112msgid "Enter Shortcode Name"
    10721113msgstr ""
     
    10841125msgstr ""
    10851126
    1086 #: includes/shortcode/class-shortcode-metabox.php:152
     1127#: includes/shortcode/class-shortcode-metabox.php:127
     1128msgid "Excerpt Length"
     1129msgstr ""
     1130
     1131#: includes/shortcode/class-shortcode-metabox.php:260
    10871132msgid "Exclude Categories"
    10881133msgstr ""
     
    10921137msgstr ""
    10931138
    1094 #: includes/shortcode/class-shortcode-metabox.php:188
     1139#: includes/shortcode/class-shortcode-metabox.php:296
    10951140#: includes/elementor/traits/trait-query-builder.php:210
    10961141msgid "Exclude Posts"
    10971142msgstr ""
    10981143
    1099 #: includes/shortcode/class-shortcode-metabox.php:176
     1144#: includes/shortcode/class-shortcode-metabox.php:284
    11001145msgid "Exclude Tags"
    11011146msgstr ""
     
    11421187msgstr ""
    11431188
    1144 #. Description of the plugin
    1145 msgid ""
    1146 "Fancy Post Grid lets you showcase posts in 9+ modern styles with full "
    1147 "support for Gutenberg blocks, Elementor widgets, and shortcodes — simple, "
    1148 "responsive, and customizable."
    1149 msgstr ""
    1150 
    11511189#: includes/admin/view/welcome.php:27
    11521190msgid "Fancy Post Grid Tutorial"
    11531191msgstr ""
    11541192
    1155 #: includes/shortcode/class-shortcode-metabox.php:78
     1193#: includes/shortcode/class-shortcode-metabox.php:158
    11561194#: includes/elementor/traits/trait-query-builder.php:71
    11571195msgid "Featured Posts Only"
     
    12301268msgstr ""
    12311269
     1270#: includes/shortcode/class-shortcode-metabox.php:806
     1271msgid "Full"
     1272msgstr ""
     1273
    12321274#: includes/admin/view/site-builder.php:115
    12331275msgid "Full Site Editing"
     
    12821324msgstr ""
    12831325
    1284 #: includes/shortcode/class-shortcode-metabox.php:45
     1326#: includes/shortcode/class-shortcode-metabox.php:41
    12851327msgid "Grid"
    12861328msgstr ""
     
    15391581msgstr ""
    15401582
     1583#: includes/shortcode/class-shortcode-metabox.php:222
     1584msgid "ID"
     1585msgstr ""
     1586
    15411587#: includes/elementor/traits/trait-slider-controls.php:600
    15421588msgid ""
     
    15941640msgstr ""
    15951641
    1596 #: includes/shortcode/class-shortcode-metabox.php:140
     1642#: includes/shortcode/class-shortcode-metabox.php:248
    15971643msgid "Include Categories"
    15981644msgstr ""
    15991645
    1600 #: includes/shortcode/class-shortcode-metabox.php:164
     1646#: includes/shortcode/class-shortcode-metabox.php:272
    16011647msgid "Include Tags"
    16021648msgstr ""
     
    16491695msgstr ""
    16501696
    1651 #: includes/shortcode/class-shortcode-metabox.php:36
    1652 msgid "Layout"
    1653 msgstr ""
    1654 
    1655 #: includes/shortcode/class-shortcode-metabox.php:56
    1656 msgid "Layout One"
    1657 msgstr ""
    1658 
    1659 #: includes/shortcode/class-shortcode-metabox.php:58
    1660 msgid "Layout Three"
    1661 msgstr ""
    1662 
    1663 #: includes/shortcode/class-shortcode-metabox.php:57
    1664 msgid "Layout Two"
    1665 msgstr ""
    1666 
    1667 #: includes/shortcode/class-shortcode-metabox.php:42
     1697#: includes/shortcode/class-shortcode-metabox.php:766
     1698msgid "Large"
     1699msgstr ""
     1700
     1701#: includes/shortcode/class-shortcode-metabox.php:33
     1702msgid "Layout Settings"
     1703msgstr ""
     1704
     1705#: includes/shortcode/class-shortcode-metabox.php:38
    16681706msgid "Layout Type"
    16691707msgstr ""
     
    17691807msgstr ""
    17701808
     1809#: includes/shortcode/class-shortcode-metabox.php:756
     1810msgid "Medium"
     1811msgstr ""
     1812
     1813#: includes/shortcode/class-shortcode-metabox.php:761
     1814msgid "Medium Large"
     1815msgstr ""
     1816
    17711817#: includes/admin/view/site-builder.php:95
    17721818msgid "Mega Menu"
     
    17751821#: includes/elementor/widgets/class-icon-box.php:233
    17761822msgid "Menu Mode?"
     1823msgstr ""
     1824
     1825#: includes/shortcode/class-shortcode-metabox.php:230
     1826msgid "Menu Order"
    17771827msgstr ""
    17781828
     
    17991849msgstr ""
    18001850
     1851#: includes/shortcode/class-shortcode-metabox.php:227
     1852msgid "Modified"
     1853msgstr ""
     1854
    18011855#: includes/elementor/widgets/class-date-time.php:189
    18021856msgid "Month Day, Year"
     
    18191873msgstr ""
    18201874
     1875#: includes/shortcode/class-shortcode-metabox.php:225
     1876msgid "Name"
     1877msgstr ""
     1878
    18211879#: includes/elementor/traits/trait-slider-controls.php:359
    18221880msgid "Nav Style"
     
    18351893msgstr ""
    18361894
    1837 #: includes/shortcode/class-shortcode-cpt.php:36
     1895#: includes/shortcode/class-shortcode-cpt.php:38
    18381896msgid "New Shortcode"
    18391897msgstr ""
     
    18791937msgstr ""
    18801938
    1881 #: includes/shortcode/class-shortcode-renderer.php:73
     1939#: includes/shortcode/class-shortcode-renderer.php:115
    18821940msgid "No posts found."
    18831941msgstr ""
    18841942
    1885 #: includes/shortcode/class-shortcode-cpt.php:39
     1943#: includes/shortcode/class-shortcode-cpt.php:41
    18861944msgid "No Shortcodes found"
    18871945msgstr ""
    18881946
    1889 #: includes/shortcode/class-shortcode-cpt.php:40
     1947#: includes/shortcode/class-shortcode-cpt.php:42
    18901948msgid "No Shortcodes found in Trash"
    18911949msgstr ""
     
    19001958msgstr ""
    19011959
     1960#: includes/shortcode/class-shortcode-metabox.php:221
    19021961#: includes/elementor/traits/trait-button-helper.php:168
    19031962#: includes/elementor/traits/trait-button-helper.php:187
     
    19271986msgstr ""
    19281987
    1929 #: includes/shortcode/class-shortcode-metabox.php:121
     1988#: includes/shortcode/class-shortcode-metabox.php:201
    19301989msgid "Number of posts to skip."
    19311990msgstr ""
     
    19452004msgstr ""
    19462005
    1947 #: includes/shortcode/class-shortcode-metabox.php:120
     2006#: includes/shortcode/class-shortcode-metabox.php:200
    19482007#: includes/elementor/traits/trait-query-builder.php:137
    19492008msgid "Offset"
     
    19722031#: includes/admin/view/settings.php:93
    19732032msgid "OpenRouter Settings"
     2033msgstr ""
     2034
     2035#: includes/shortcode/class-shortcode-metabox.php:208
     2036msgid "Order"
     2037msgstr ""
     2038
     2039#: includes/shortcode/class-shortcode-metabox.php:218
     2040msgid "Order By"
    19742041msgstr ""
    19752042
     
    20242091msgstr ""
    20252092
    2026 #: includes/shortcode/class-shortcode-metabox.php:96
     2093#: includes/shortcode/class-shortcode-metabox.php:176
    20272094msgid "Popular Posts Only"
    20282095msgstr ""
     
    20792146msgstr ""
    20802147
    2081 #: includes/shortcode/class-shortcode-metabox.php:102
     2148#: includes/shortcode/class-shortcode-metabox.php:182
    20822149msgid "Posts By"
    20832150msgstr ""
    20842151
    2085 #: includes/shortcode/class-shortcode-metabox.php:113
     2152#: includes/shortcode/class-shortcode-metabox.php:193
    20862153msgid "Posts Per Page"
    20872154msgstr ""
     
    21562223msgstr ""
    21572224
    2158 #: includes/shortcode/class-shortcode-metabox.php:72
     2225#: includes/shortcode/class-shortcode-metabox.php:153
    21592226#: includes/elementor/traits/trait-query-builder.php:65
    21602227msgid "Query Builder"
     
    21632230#: includes/elementor/widgets/class-post-grid.php:72
    21642231msgid "Query Type"
     2232msgstr ""
     2233
     2234#: includes/shortcode/class-shortcode-metabox.php:228
     2235msgid "Random"
     2236msgstr ""
     2237
     2238#: includes/shortcode/class-shortcode-metabox.php:134
     2239#: includes/shortcode/class-shortcode-metabox.php:141
     2240#: includes/shortcode/templates/card-one.php:56
     2241msgid "Read More"
     2242msgstr ""
     2243
     2244#: includes/shortcode/class-shortcode-metabox.php:140
     2245msgid "Read More Text"
    21652246msgstr ""
    21662247
     
    22422323msgstr ""
    22432324
     2325#: includes/shortcode/class-shortcode-metabox.php:77
     2326msgid "Row Gap"
     2327msgstr ""
     2328
    22442329#: includes/elementor/traits/trait-slider-controls.php:998
    22452330#: includes/elementor/widgets/class-date-time.php:298
     
    22672352msgstr ""
    22682353
     2354#: includes/shortcode/class-shortcode-cpt.php:121
     2355msgid "Save this shortcode to generate its ID."
     2356msgstr ""
     2357
    22692358#: includes/elementor/widgets/class-image.php:142
    22702359msgid "Scale"
     
    23012390msgstr ""
    23022391
    2303 #: includes/shortcode/class-shortcode-cpt.php:38
     2392#: includes/shortcode/class-shortcode-cpt.php:40
    23042393msgid "Search Shortcodes"
    23052394msgstr ""
     
    23132402msgstr ""
    23142403
    2315 #: includes/shortcode/class-shortcode-metabox.php:153
     2404#: includes/shortcode/class-shortcode-metabox.php:261
    23162405msgid "Select categories to exclude."
    23172406msgstr ""
    23182407
    2319 #: includes/shortcode/class-shortcode-metabox.php:141
     2408#: includes/shortcode/class-shortcode-metabox.php:249
    23202409msgid "Select categories to include."
    23212410msgstr ""
     
    23332422msgstr ""
    23342423
    2335 #: includes/shortcode/class-shortcode-metabox.php:189
     2424#: includes/shortcode/class-shortcode-metabox.php:297
    23362425msgid "Select posts to exclude."
    23372426msgstr ""
     
    23412430msgstr ""
    23422431
    2343 #: includes/shortcode/class-shortcode-metabox.php:128
     2432#: includes/shortcode/class-shortcode-metabox.php:236
    23442433#: includes/elementor/traits/trait-query-builder.php:146
    23452434msgid "Select Specific Posts"
     
    23502439msgstr ""
    23512440
    2352 #: includes/shortcode/class-shortcode-metabox.php:177
     2441#: includes/shortcode/class-shortcode-metabox.php:285
    23532442msgid "Select tags to exclude."
    23542443msgstr ""
    23552444
    2356 #: includes/shortcode/class-shortcode-metabox.php:165
     2445#: includes/shortcode/class-shortcode-metabox.php:273
    23572446msgid "Select tags to include."
    23582447msgstr ""
    23592448
    2360 #: includes/shortcode/class-shortcode-metabox.php:43
     2449#: includes/shortcode/class-shortcode-metabox.php:39
    23612450msgid "Select the layout type for displaying posts."
    23622451msgstr ""
     
    23892478msgstr ""
    23902479
    2391 #: includes/shortcode/class-shortcode-cpt.php:32
    2392 #: includes/shortcode/class-shortcode-cpt.php:71
     2480#: includes/shortcode/class-shortcode-cpt.php:34
     2481#: includes/shortcode/class-shortcode-cpt.php:81
     2482#: includes/shortcode/class-shortcode-cpt.php:111
    23932483#: includes/admin/view/settings.php:42
    23942484msgid "Shortcode"
     
    23992489msgstr ""
    24002490
    2401 #: includes/shortcode/class-shortcode-metabox.php:220
     2491#: includes/shortcode/class-shortcode-metabox.php:336
    24022492msgid "Shortcode Settings"
    24032493msgstr ""
    24042494
    24052495#: includes/admin/class-dashboard.php:36
    2406 #: includes/shortcode/class-shortcode-cpt.php:31
     2496#: includes/shortcode/class-shortcode-cpt.php:33
    24072497msgid "Shortcodes"
    24082498msgstr ""
     
    24222512msgstr ""
    24232513
     2514#: includes/shortcode/class-shortcode-metabox.php:147
     2515msgid "Show Category"
     2516msgstr ""
     2517
    24242518#: includes/elementor/widgets/class-date-time.php:170
    24252519msgid "Show Date"
     
    24302524msgstr ""
    24312525
     2526#: includes/shortcode/class-shortcode-metabox.php:121
     2527msgid "Show Excerpt"
     2528msgstr ""
     2529
    24322530#: includes/elementor/widgets/class-social-icons.php:64
    24332531msgid "Show Icons"
    24342532msgstr ""
    24352533
    2436 #: includes/shortcode/class-shortcode-metabox.php:66
    2437 msgid "Show pagination for posts."
    2438 msgstr ""
    2439 
    24402534#: includes/elementor/widgets/class-divider.php:77
    24412535msgid "Show Progress"
     
    24482542#: includes/elementor/widgets/class-social-icons.php:76
    24492543msgid "Show Social Texts"
     2544msgstr ""
     2545
     2546#: includes/shortcode/class-shortcode-metabox.php:107
     2547msgid "Show Thumbnail"
    24502548msgstr ""
    24512549
     
    24942592msgstr ""
    24952593
    2496 #: includes/shortcode/class-shortcode-metabox.php:46
     2594#: includes/shortcode/class-shortcode-metabox.php:42
    24972595msgid "Slider"
    24982596msgstr ""
     
    25402638msgstr ""
    25412639
     2640#: includes/shortcode/class-shortcode-metabox.php:71
     2641msgid "Space between per column."
     2642msgstr ""
     2643
     2644#: includes/shortcode/class-shortcode-metabox.php:78
     2645msgid "Space between per row."
     2646msgstr ""
     2647
    25422648#: includes/elementor/widgets/class-social-icons.php:262
    25432649#: includes/elementor/widgets/class-social-icons.php:400
     
    25452651msgstr ""
    25462652
    2547 #: includes/shortcode/class-shortcode-metabox.php:106
     2653#: includes/shortcode/class-shortcode-metabox.php:186
    25482654#: includes/elementor/traits/trait-query-builder.php:125
    25492655msgid "Specific Posts"
     
    27422848msgstr ""
    27432849
     2850#: includes/shortcode/class-shortcode-metabox.php:751
     2851msgid "Thumbnail"
     2852msgstr ""
     2853
     2854#: includes/shortcode/class-shortcode-metabox.php:113
     2855msgid "Thumbnail Size"
     2856msgstr ""
     2857
    27442858#: includes/elementor/widgets/class-date-time.php:58
    27452859msgid "Time"
     
    27622876msgstr ""
    27632877
     2878#: includes/shortcode/class-shortcode-metabox.php:223
    27642879#: includes/elementor/widgets/class-icon-box.php:929
    27652880msgid "Title"
     
    27732888#: includes/elementor/widgets/class-icon-box.php:322
    27742889msgid "Title HTML Tag"
     2890msgstr ""
     2891
     2892#: includes/shortcode/class-shortcode-metabox.php:90
     2893msgid "Title Tag"
    27752894msgstr ""
    27762895
     
    28262945msgstr ""
    28272946
    2828 #: includes/shortcode/class-shortcode-metabox.php:84
     2947#: includes/shortcode/class-shortcode-metabox.php:164
    28292948#: includes/elementor/traits/trait-query-builder.php:80
    28302949msgid "Trending Posts Only"
     
    29403059msgstr ""
    29413060
    2942 #: includes/shortcode/class-shortcode-cpt.php:37
     3061#: includes/shortcode/class-shortcode-cpt.php:39
    29433062msgid "View Shortcode"
    29443063msgstr ""
  • fancy-post-grid/trunk/readme.txt

    r3456954 r3464012  
    55Tested up to: 6.9
    66Requires PHP: 7.4
    7 Stable tag: 2.0.1
     7Stable tag: 2.0.2
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    8989== Changelog ==
    9090
     91= 2.0.2 (18 Feb, 2026) =
     92Fixed:
     93* Missing Shortcode Module styles
     94
    9195= 2.0.1 (09 Feb, 2026) =
    9296Added:
Note: See TracChangeset for help on using the changeset viewer.