Make WordPress Themes

Changeset 356198


Ignore:
Timestamp:
10/29/2026 04:09:14 PM (17 hours ago)
Author:
themedropbox
Message:

New version of TSO Blog - 1.0.9

Location:
tso-blog/1.0.9
Files:
3 added
5 deleted
15 edited
1 copied

Legend:

Unmodified
Added
Removed
  • tso-blog/1.0.9/class-tsotheme-customize-repeater-control.php

    r352803 r356198  
    5858
    5959        /**
     60         * Maximum number of rows. The pricing repeater uses 5.
     61         *
     62         * @var int
     63         */
     64        public $max_items = 50;
     65
     66        /**
    6067         * Enqueue control scripts/styles.
    6168         */
    … …  
    94101         */
    95102        protected function get_rows() {
    96                 $raw = $this->value();
     103                $raw  = $this->value();
    97104                $rows = json_decode( is_string( $raw ) ? $raw : '', true );
    98                 return is_array( $rows ) ? array_values( $rows ) : array();
     105                $rows = is_array( $rows ) ? array_values( $rows ) : array();
     106                $max  = absint( $this->max_items );
     107                if ( $max > 0 && count( $rows ) > $max ) {
     108                        $rows = array_slice( $rows, 0, $max );
     109                }
     110                return $rows;
    99111        }
    100112
    … …  
    118130                        data-fields="<?php echo esc_attr( wp_json_encode( $this->fields ) ); ?>"
    119131                        data-add-label="<?php echo esc_attr( $this->add_label ? $this->add_label : __( 'Add row', 'tso-blog' ) ); ?>"
     132                        data-max="<?php echo esc_attr( (string) max( 1, absint( $this->max_items ) ) ); ?>"
    120133                >
    121134                        <ul class="tso-repeater-list"></ul>
  • tso-blog/1.0.9/functions.php

    r353346 r356198  
    77
    88if ( ! defined( 'TSOTHM_VERSION' ) ) {
    9     define( 'TSOTHM_VERSION', '1.0.8' );
     9    define( 'TSOTHM_VERSION', '1.0.9' );
    1010}
    1111
    … …  
    12401240                true
    12411241            );
     1242            wp_localize_script(
     1243                'tsothm-lightbox',
     1244                'tsothmLightbox',
     1245                array(
     1246                    'viewer' => __( 'Image viewer', 'tso-blog' ),
     1247                    'close'  => __( 'Close', 'tso-blog' ),
     1248                    'prev'   => __( 'Previous image', 'tso-blog' ),
     1249                    'next'   => __( 'Next image', 'tso-blog' ),
     1250                )
     1251            );
    12421252        }
    12431253    }
    … …  
    23592369        $wp_customize->get_section( 'title_tagline' )->priority = 20;
    23602370    }
    2361 
    2362     $wp_customize->add_panel(
    2363         'tsothm_appearance_panel',
    2364         array(
    2365             'title'       => __( 'Appearance', 'tso-blog' ),
    2366             'description' => __( 'Logo, site width, sidebar, header social links and header image.', 'tso-blog' ),
    2367             'priority'    => 25,
    2368         )
    2369     );
    2370 
    2371     $wp_customize->add_panel(
    2372         'tsothm_footer_panel',
    2373         array(
    2374             'title'       => __( 'Footer', 'tso-blog' ),
    2375             'description' => __( 'Help for widget columns and footer texts. Footer colors are under Colors → Footer.', 'tso-blog' ),
    2376             'priority'    => 40,
    2377         )
    2378     );
    2379 
    2380     $wp_customize->add_panel(
    2381         'tsothm_content_panel',
    2382         array(
    2383             'title'       => __( 'Templates', 'tso-blog' ),
    2384             'description' => __( 'Contact, Team, Pricing and Landing templates, and the top announcement.', 'tso-blog' ),
    2385             'priority'    => 45,
    2386         )
    2387     );
    2388 
    2389     if ( $wp_customize->get_section( 'header_image' ) ) {
    2390         $wp_customize->get_section( 'header_image' )->panel    = 'tsothm_appearance_panel';
    2391         $wp_customize->get_section( 'header_image' )->priority = 40;
    2392     }
    2393     if ( $wp_customize->get_section( 'background_image' ) ) {
    2394         $wp_customize->get_section( 'background_image' )->panel       = 'tsothm_appearance_panel';
    2395         $wp_customize->get_section( 'background_image' )->priority    = 15;
    2396         $wp_customize->get_section( 'background_image' )->description = __( 'Optional background image for the whole site, behind the content. You can upload it and adjust its position, size, repeat and scrolling (parallax).', 'tso-blog' );
    2397     }
    2398     if ( $wp_customize->get_section( 'colors' ) ) {
    2399         // Core “Colores” vacío: ocultar para no duplicar el panel TSO.
    2400         $wp_customize->remove_section( 'colors' );
    2401     }
    2402 
    2403     /* Logo — imagen (core) + tamaños */
    2404     $wp_customize->add_section(
    2405         'tsothm_logo_section',
    2406         array(
    2407             'title'       => __( 'Logo', 'tso-blog' ),
    2408             'description' => __( 'Upload the logo and set its width on desktop and mobile. There is no sticky logo: the header does not stay fixed on scroll.', 'tso-blog' ),
    2409             'panel'       => 'tsothm_appearance_panel',
    2410             'priority'    => 10,
    2411         )
    2412     );
    2413 
    2414     if ( $wp_customize->get_control( 'custom_logo' ) ) {
    2415         $wp_customize->get_control( 'custom_logo' )->section  = 'tsothm_logo_section';
    2416         $wp_customize->get_control( 'custom_logo' )->priority = 5;
    2417         $wp_customize->get_control( 'custom_logo' )->label    = __( 'Logo', 'tso-blog' );
    2418     }
    2419 
     2371    // Keep Site Title and Tagline after the Site Logo width controls (priorities 9 and 10).
     2372    if ( $wp_customize->get_control( 'blogname' ) ) {
     2373        $wp_customize->get_control( 'blogname' )->priority = 20;
     2374    }
     2375    if ( $wp_customize->get_control( 'blogdescription' ) ) {
     2376        $wp_customize->get_control( 'blogdescription' )->priority = 30;
     2377    }
     2378
     2379    /*
     2380     * Logo widths live next to the core Site Logo control (Site Identity).
     2381     * Do not move or replace custom_logo: that is the WordPress Site Logo feature.
     2382     */
    24202383    $wp_customize->add_setting(
    24212384        'tsothm_logo_width',
    … …  
    24302393        array(
    24312394            'label'       => __( 'Logo width (px)', 'tso-blog' ),
    2432             'description' => __( 'Desktop display width. Theme maximum: 500 px (minimum 40). Height adjusts automatically. For a sharp result, use a logo file at least this wide.', 'tso-blog' ),
    2433             'section'     => 'tsothm_logo_section',
     2395            'description' => __( 'Desktop display width of the Site Logo. Theme maximum: 500 px (minimum 40). Height adjusts automatically. For a sharp result, use a logo file at least this wide.', 'tso-blog' ),
     2396            'section'     => 'title_tagline',
     2397            'type'        => 'number',
     2398            'priority'    => 9,
     2399            'input_attrs' => array(
     2400                'min'  => 40,
     2401                'max'  => 500,
     2402                'step' => 1,
     2403            ),
     2404        )
     2405    );
     2406
     2407    $wp_customize->add_setting(
     2408        'tsothm_logo_width_mobile',
     2409        array(
     2410            'default'           => '180',
     2411            'sanitize_callback' => 'tsothm_sanitize_logo_width_mobile',
     2412            'transport'         => 'refresh',
     2413        )
     2414    );
     2415    $wp_customize->add_control(
     2416        'tsothm_logo_width_mobile',
     2417        array(
     2418            'label'       => __( 'Logo width on mobile (px)', 'tso-blog' ),
     2419            'description' => __( 'Mobile display width of the Site Logo (screens ≤ 768 px). Theme maximum: 500 px (minimum 40). Use the Customizer mobile preview to see it. For a sharp result, use a logo file at least this wide.', 'tso-blog' ),
     2420            'section'     => 'title_tagline',
    24342421            'type'        => 'number',
    24352422            'priority'    => 10,
    … …  
    24422429    );
    24432430
    2444     $wp_customize->add_setting(
    2445         'tsothm_logo_width_mobile',
    2446         array(
    2447             'default'           => '180',
    2448             'sanitize_callback' => 'tsothm_sanitize_logo_width_mobile',
    2449             'transport'         => 'refresh',
    2450         )
    2451     );
    2452     $wp_customize->add_control(
    2453         'tsothm_logo_width_mobile',
    2454         array(
    2455             'label'       => __( 'Logo width on mobile (px)', 'tso-blog' ),
    2456             'description' => __( 'Mobile display width (screens ≤ 768 px). Theme maximum: 500 px (minimum 40). Use the Customizer mobile preview to see it. For a sharp result, use a logo file at least this wide.', 'tso-blog' ),
    2457             'section'     => 'tsothm_logo_section',
    2458             'type'        => 'number',
    2459             'priority'    => 20,
    2460             'input_attrs' => array(
    2461                 'min'  => 40,
    2462                 'max'  => 500,
    2463                 'step' => 1,
    2464             ),
    2465         )
    2466     );
     2431    $wp_customize->add_panel(
     2432        'tsothm_appearance_panel',
     2433        array(
     2434            'title'       => __( 'Appearance', 'tso-blog' ),
     2435            'description' => __( 'Site width, sidebar, header social links and header image. The site logo is set under Site Identity.', 'tso-blog' ),
     2436            'priority'    => 25,
     2437        )
     2438    );
     2439
     2440    $wp_customize->add_panel(
     2441        'tsothm_footer_panel',
     2442        array(
     2443            'title'       => __( 'Footer', 'tso-blog' ),
     2444            'description' => __( 'Help for widget columns and footer texts. Footer colors are under Colors → Footer.', 'tso-blog' ),
     2445            'priority'    => 40,
     2446        )
     2447    );
     2448
     2449    $wp_customize->add_panel(
     2450        'tsothm_content_panel',
     2451        array(
     2452            'title'       => __( 'Templates', 'tso-blog' ),
     2453            'description' => __( 'Contact, Team, Pricing and Landing templates, and the top announcement.', 'tso-blog' ),
     2454            'priority'    => 45,
     2455        )
     2456    );
     2457
     2458    if ( $wp_customize->get_section( 'header_image' ) ) {
     2459        $wp_customize->get_section( 'header_image' )->panel    = 'tsothm_appearance_panel';
     2460        $wp_customize->get_section( 'header_image' )->priority = 40;
     2461    }
     2462    if ( $wp_customize->get_section( 'background_image' ) ) {
     2463        $wp_customize->get_section( 'background_image' )->panel       = 'tsothm_appearance_panel';
     2464        $wp_customize->get_section( 'background_image' )->priority    = 15;
     2465        $wp_customize->get_section( 'background_image' )->description = __( 'Optional background image for the whole site, behind the content. You can upload it and adjust its position, size, repeat and scrolling (parallax).', 'tso-blog' );
     2466    }
     2467    if ( $wp_customize->get_section( 'colors' ) ) {
     2468        // Core “Colores” vacío: ocultar para no duplicar el panel TSO.
     2469        $wp_customize->remove_section( 'colors' );
     2470    }
    24672471
    24682472    /* Diseño — ancho del sitio */
    … …  
    38173821            'description' => __( 'In Google Maps: Share → Embed a map → copy the src attribute URL (or paste the full embed code). Only Google Maps embed URLs are accepted. Empty = no map.', 'tso-blog' ),
    38183822            'section'     => 'tsothm_contact_section',
    3819             'type'        => 'textarea',
     3823            'type'        => 'text',
    38203824        )
    38213825    );
    … …  
    38923896        array(
    38933897            'title'       => __( 'Pricing', 'tso-blog' ),
    3894             'description' => __( 'Plans for the "Pricing" page template. If left empty, the page will show the block content ("Pricing (3 plans)" pattern).', 'tso-blog' ),
     3898            'description' => __( 'Up to 5 plans for the "Pricing" page template. If left empty, the page shows the block content ("Pricing (3 plans)" pattern).', 'tso-blog' ),
    38953899            'panel'       => 'tsothm_content_panel',
    38963900            'priority'    => 24,
    … …  
    39103914            'tsothm_pricing_plans',
    39113915            array(
    3912                 'label'      => __( 'Plans', 'tso-blog' ),
    3913                 'section'    => 'tsothm_pricing_section',
    3914                 'fields'     => tsothm_get_pricing_fields(),
    3915                 'add_label'  => __( 'Add plan', 'tso-blog' ),
     3916                'label'       => __( 'Plans', 'tso-blog' ),
     3917                'description' => __( 'Maximum 5 plans. Separate features with a vertical bar, for example: Support | Updates | Backups.', 'tso-blog' ),
     3918                'section'     => 'tsothm_pricing_section',
     3919                'fields'      => tsothm_get_pricing_fields(),
     3920                'add_label'   => __( 'Add plan', 'tso-blog' ),
     3921                'max_items'   => 5,
    39163922            )
    39173923        )
    … …  
    57035709                array(
    57045710                        'key'   => 'features',
    5705                         'type'  => 'textarea',
    5706                         'label' => __( 'Features (one per line)', 'tso-blog' ),
     5711                        'type'  => 'text',
     5712                        'label' => __( 'Features, separated by |', 'tso-blog' ),
    57075713                ),
    57085714                array(
    … …  
    57435749                array(
    57445750                        'key'   => 'text',
    5745                         'type'  => 'textarea',
     5751                        'type'  => 'text',
    57465752                        'label' => __( 'Short text', 'tso-blog' ),
    57475753                ),
    … …  
    57675773                default:
    57685774                        $text = is_string( $value ) ? $value : '';
     5775                        // Older pricing rows stored one feature per line. Keep them as separate items.
     5776                        if ( isset( $field['key'] ) && 'features' === $field['key'] ) {
     5777                                $text = str_replace( array( "\r\n", "\r", "\n" ), ' | ', $text );
     5778                        }
    57695779                        // Los campos de texto que parecen URL (link, button_url) se sanean como URL.
    57705780                        if ( isset( $field['key'] ) && false !== strpos( $field['key'], 'url' ) ) {
    … …  
    57805790/**
    57815791 * Generic repeater sanitizer: decodes JSON, keeps only known fields,
    5782  * sanitizes each value by type, re-encodes to JSON. Caps at 50 rows.
     5792 * sanitizes each value by type, re-encodes to JSON.
    57835793 *
    57845794 * @param string $value  Raw JSON value from the Customizer control.
    57855795 * @param array  $fields Field schema (tsothm_get_*_fields()).
     5796 * @param int    $max    Maximum rows. Pricing uses 5; other repeaters default to 50.
    57865797 * @return string Sanitized JSON.
    57875798 */
    5788 function tsothm_sanitize_repeater_json( $value, $fields ) {
     5799function tsothm_sanitize_repeater_json( $value, $fields, $max = 50 ) {
    57895800        $rows = json_decode( is_string( $value ) ? $value : '', true );
    57905801        if ( ! is_array( $rows ) ) {
    … …  
    57925803        }
    57935804
     5805        $max = absint( $max );
     5806        if ( $max < 1 ) {
     5807                $max = 50;
     5808        }
     5809
    57945810        $clean = array();
    5795         foreach ( array_slice( $rows, 0, 50 ) as $row ) {
     5811        foreach ( array_slice( $rows, 0, $max ) as $row ) {
    57965812                if ( ! is_array( $row ) ) {
    57975813                        continue;
    … …  
    58285844 */
    58295845function tsothm_sanitize_pricing_plans( $value ) {
    5830         return tsothm_sanitize_repeater_json( $value, tsothm_get_pricing_fields() );
     5846        return tsothm_sanitize_repeater_json( $value, tsothm_get_pricing_fields(), 5 );
    58315847}
    58325848
    … …  
    59805996                if ( ! empty( $plan['features'] ) ) {
    59815997                        echo '<ul class="tso-pricing-features">';
    5982                         foreach ( preg_split( '/\r\n|\r|\n/', (string) $plan['features'] ) as $line ) {
     5998                        foreach ( preg_split( '/\s*\|\s*|\r\n|\r|\n/', (string) $plan['features'] ) as $line ) {
    59835999                                $line = trim( $line );
    59846000                                if ( '' === $line ) {
    … …  
    67146730    };
    67156731
    6716     // Site title (fallback text logo when no custom logo / theme logo file).
     6732    // Site title (shown when Site Identity has no Site Logo).
    67176733    $set_post_message( 'blogname' );
    67186734    $wp_customize->selective_refresh->add_partial(
  • tso-blog/1.0.9/header.php

    r353346 r356198  
    1717                    <?php the_custom_logo(); ?>
    1818                <?php else : ?>
    19                     <a href="<?php echo esc_url( home_url( '/' ) ); ?>">
    20                         <?php
    21                         $tsothm_logo = get_stylesheet_directory() . '/images/logo.png';
    22                         if ( file_exists( $tsothm_logo ) ) {
    23                             echo '<img src="' . esc_url( get_stylesheet_directory_uri() . '/images/logo.png' ) . '" alt="' . esc_attr( get_bloginfo( 'name' ) ) . '" width="300" height="124">';
    24                         } else {
    25                             echo '<span class="site-title-text">' . esc_html( get_bloginfo( 'name' ) ) . '</span>';
    26                         }
    27                         ?>
     19                    <a href="<?php echo esc_url( home_url( '/' ) ); ?>" rel="home">
     20                        <span class="site-title-text"><?php echo esc_html( get_bloginfo( 'name', 'display' ) ); ?></span>
    2821                    </a>
    2922                <?php endif; ?>
    … …  
    6558        <?php tsothm_header_announcement(); ?>
    6659
    67         <nav id="site-navigation" class="main-navigation" aria-label="Menú principal">
     60        <nav id="site-navigation" class="main-navigation" aria-label="<?php esc_attr_e( 'Primary Menu', 'tso-blog' ); ?>">
    6861            <?php wp_nav_menu( array(
    6962                'theme_location' => 'main-menu',
  • tso-blog/1.0.9/languages/tso-blog-ca.po

    r353345 r356198  
    451451#: functions.php:2352
    452452msgid ""
    453 "Desktop display width. Theme maximum: 500 px (minimum 40). Height adjusts "
    454 "automatically. For a sharp result, use a logo file at least this wide."
    455 msgstr ""
    456 "Amplada de visualització a l'escriptori. Màxim del tema: 500 px (mínim 40). "
    457 "L'alçada s'ajusta sola. Per un resultat nítid, useu un fitxer de logo "
    458 "d'almenys aquesta amplada."
     453"Desktop display width of the Site Logo. Theme maximum: 500 px (minimum 40). "
     454"Height adjusts automatically. For a sharp result, use a logo file at least "
     455"this wide."
     456msgstr ""
     457"Amplada de visualització del logotip del lloc a l'escriptori. Màxim del "
     458"tema: 500 px (mínim 40). L'alçada s'ajusta sola. Per un resultat nítid, "
     459"useu un fitxer de logo d'almenys aquesta amplada."
    459460
    460461#: functions.php:1638
    … …  
    533534
    534535#: functions.php:5316
    535 msgid "Features (one per line)"
    536 msgstr "Característiques (una per línia)"
     536msgid "Features, separated by |"
     537msgstr "Característiques, separades per |"
    537538
    538539#: functions.php:4797
    … …  
    867868
    868869#: functions.php:2286
    869 msgid "Logo, site width, sidebar, header social links and header image."
    870 msgstr ""
    871 "Logotip, amplada del lloc, barra lateral, xarxes socials de la capçalera i "
    872 "imatge de capçalera."
     870msgid ""
     871"Site width, sidebar, header social links and header image. The site logo is "
     872"set under Site Identity."
     873msgstr ""
     874"Amplada del lloc, barra lateral, xarxes socials de la capçalera i imatge de "
     875"capçalera. El logotip del lloc es configura a Identitat del lloc."
    873876
    874877#: functions.php:3214
    … …  
    931934#: functions.php:2376
    932935msgid ""
    933 "Mobile display width (screens ≤ 768 px). Theme maximum: 500 px (minimum 40)."
    934 " Use the Customizer mobile preview to see it. For a sharp result, use a logo"
    935 " file at least this wide."
    936 msgstr ""
    937 "Amplada de visualització al mòbil (pantalles ≤ 768 px). Màxim del tema: 500 "
    938 "px (mínim 40). Useu la vista prèvia mòbil del Personalitzador per veure-ho. "
    939 "Per un resultat nítid, useu un fitxer de logo d'almenys aquesta amplada."
     936"Mobile display width of the Site Logo (screens ≤ 768 px). Theme maximum: "
     937"500 px (minimum 40). Use the Customizer mobile preview to see it. For a "
     938"sharp result, use a logo file at least this wide."
     939msgstr ""
     940"Amplada de visualització del logotip del lloc al mòbil (pantalles ≤ 768 "
     941"px). Màxim del tema: 500 px (mínim 40). Useu la vista prèvia mòbil del "
     942"Personalitzador per veure-ho. Per un resultat nítid, useu un fitxer de logo "
     943"d'almenys aquesta amplada."
    940944
    941945#: tsothm-mobile-menu.php:76 tsothm-mobile-menu.php:187
    … …  
    11351139#: functions.php:3548
    11361140msgid ""
    1137 "Plans for the \"Pricing\" page template. If left empty, the page will show "
    1138 "the block content (\"Pricing (3 plans)\" pattern)."
    1139 msgstr ""
    1140 "Plans per a la plantilla de pàgina «Precios». Si ho deixes buit, la pàgina "
    1141 "mostrarà el contingut de blocs (patró «Precios (3 planes)»)."
     1141"Up to 5 plans for the \"Pricing\" page template. If left empty, the page "
     1142"shows the block content (\"Pricing (3 plans)\" pattern)."
     1143msgstr ""
     1144"Fins a 5 plans per a la plantilla de pàgina «Precios». Si ho deixes buit, la "
     1145"pàgina mostra el contingut de blocs (patró «Precios (3 planes)»)."
     1146
     1147#: functions.php
     1148msgid ""
     1149"Maximum 5 plans. Separate features with a vertical bar, for example: "
     1150"Support | Updates | Backups."
     1151msgstr ""
     1152"Màxim 5 plans. Separa les característiques amb una barra vertical, per "
     1153"exemple: Suport | Actualitzacions | Còpies."
    11421154
    11431155#: functions.php:1714
    … …  
    18551867"Família tipogràfica i estil del text de la barra de novetats / anuncis entre"
    18561868" la capçalera i el menú. Els colors són a Colors → Anunci."
     1869
     1870#: style.css
     1871msgid "A classic blog theme with an orange frame and a white content area. The home page is a two-column post grid with live search and a Load more button. Posts use a right sidebar. The Customizer controls colors, typography, and the Site Logo, and includes a day and night mode. Page templates are included for contact, portfolio, pricing, team, and FAQ."
     1872msgstr "Tema de blog clàssic, amb marc taronja i àrea de contingut blanca. La portada és una graella de dues columnes, amb cerca en directe i un botó per carregar-ne més. Els articles tenen barra lateral dreta. El Personalitzador controla els colors, la tipografia i el logotip del lloc, i inclou un mode dia i nit. Hi ha plantilles de pàgina per a contacte, portafoli, preus, equip i preguntes freqüents."
     1873
     1874#: functions.php
     1875msgid "Image viewer"
     1876msgstr "Visor d'imatges"
     1877
     1878#: functions.php
     1879msgid "Close"
     1880msgstr "Tanca"
     1881
     1882#: functions.php
     1883msgid "Previous image"
     1884msgstr "Imatge anterior"
     1885
     1886#: functions.php
     1887msgid "Next image"
     1888msgstr "Imatge següent"
  • tso-blog/1.0.9/languages/tso-blog-es_ES.po

    r353345 r356198  
    453453#: functions.php:2352
    454454msgid ""
    455 "Desktop display width. Theme maximum: 500 px (minimum 40). Height adjusts "
    456 "automatically. For a sharp result, use a logo file at least this wide."
    457 msgstr ""
    458 "Ancho de visualización en escritorio. Máximo del tema: 500 px (mínimo 40). "
    459 "La altura se ajusta sola. Para un resultado nítido, use un archivo de logo "
    460 "de al menos este ancho."
     455"Desktop display width of the Site Logo. Theme maximum: 500 px (minimum 40). "
     456"Height adjusts automatically. For a sharp result, use a logo file at least "
     457"this wide."
     458msgstr ""
     459"Ancho de visualización del logotipo del sitio en escritorio. Máximo del "
     460"tema: 500 px (mínimo 40). La altura se ajusta sola. Para un resultado "
     461"nítido, use un archivo de logo de al menos este ancho."
    461462
    462463#: functions.php:1638
    … …  
    535536
    536537#: functions.php:5316
    537 msgid "Features (one per line)"
    538 msgstr "Características (una por línea)"
     538msgid "Features, separated by |"
     539msgstr "Características, separadas por |"
    539540
    540541#: functions.php:4797
    … …  
    869870
    870871#: functions.php:2286
    871 msgid "Logo, site width, sidebar, header social links and header image."
    872 msgstr ""
    873 "Logo, ancho del sitio, barra lateral, enlaces sociales de la cabecera e "
    874 "imagen de cabecera."
     872msgid ""
     873"Site width, sidebar, header social links and header image. The site logo is "
     874"set under Site Identity."
     875msgstr ""
     876"Ancho del sitio, barra lateral, enlaces sociales de la cabecera e imagen de "
     877"cabecera. El logotipo del sitio se configura en Identidad del sitio."
    875878
    876879#: functions.php:3214
    … …  
    933936#: functions.php:2376
    934937msgid ""
    935 "Mobile display width (screens ≤ 768 px). Theme maximum: 500 px (minimum 40)."
    936 " Use the Customizer mobile preview to see it. For a sharp result, use a logo"
    937 " file at least this wide."
    938 msgstr ""
    939 "Ancho de visualización en móvil (pantallas ≤ 768 px). Máximo del tema: 500 "
    940 "px (mínimo 40). Use la vista previa móvil del Personalizador para verlo. "
    941 "Para un resultado nítido, use un archivo de logo de al menos este ancho."
     938"Mobile display width of the Site Logo (screens ≤ 768 px). Theme maximum: "
     939"500 px (minimum 40). Use the Customizer mobile preview to see it. For a "
     940"sharp result, use a logo file at least this wide."
     941msgstr ""
     942"Ancho de visualización del logotipo del sitio en móvil (pantallas ≤ 768 "
     943"px). Máximo del tema: 500 px (mínimo 40). Use la vista previa móvil del "
     944"Personalizador para verlo. Para un resultado nítido, use un archivo de logo "
     945"de al menos este ancho."
    942946
    943947#: tsothm-mobile-menu.php:76 tsothm-mobile-menu.php:187
    … …  
    11391143#: functions.php:3548
    11401144msgid ""
    1141 "Plans for the \"Pricing\" page template. If left empty, the page will show "
    1142 "the block content (\"Pricing (3 plans)\" pattern)."
    1143 msgstr ""
    1144 "Planes para la plantilla de página «Precios». Si lo dejas vacío, la página "
    1145 "mostrará el contenido de bloques (patrón «Precios (3 planes)»)."
     1145"Up to 5 plans for the \"Pricing\" page template. If left empty, the page "
     1146"shows the block content (\"Pricing (3 plans)\" pattern)."
     1147msgstr ""
     1148"Hasta 5 planes para la plantilla de página «Precios». Si lo dejas vacío, la "
     1149"página muestra el contenido de bloques (patrón «Precios (3 planes)»)."
     1150
     1151#: functions.php
     1152msgid ""
     1153"Maximum 5 plans. Separate features with a vertical bar, for example: "
     1154"Support | Updates | Backups."
     1155msgstr ""
     1156"Máximo 5 planes. Separa las características con una barra vertical, por "
     1157"ejemplo: Soporte | Actualizaciones | Copias."
    11461158
    11471159#: functions.php:1714
    … …  
    18631875"Familia tipográfica y estilo del texto de la barra de novedades / anuncios "
    18641876"entre la cabecera y el menú. Los colores están en Colores → Anuncio."
     1877
     1878#: style.css
     1879msgid "A classic blog theme with an orange frame and a white content area. The home page is a two-column post grid with live search and a Load more button. Posts use a right sidebar. The Customizer controls colors, typography, and the Site Logo, and includes a day and night mode. Page templates are included for contact, portfolio, pricing, team, and FAQ."
     1880msgstr "Tema de blog clásico, con marco naranja y área de contenido blanca. La portada es una cuadrícula de dos columnas, con búsqueda en directo y un botón para cargar más. Los artículos llevan barra lateral derecha. El Personalizador controla los colores, la tipografía y el logotipo del sitio, e incluye un modo día y noche. Hay plantillas de página para contacto, portafolio, precios, equipo y preguntas frecuentes."
     1881
     1882#: functions.php
     1883msgid "Image viewer"
     1884msgstr "Visor de imágenes"
     1885
     1886#: functions.php
     1887msgid "Close"
     1888msgstr "Cerrar"
     1889
     1890#: functions.php
     1891msgid "Previous image"
     1892msgstr "Imagen anterior"
     1893
     1894#: functions.php
     1895msgid "Next image"
     1896msgstr "Imagen siguiente"
  • tso-blog/1.0.9/readme.txt

    r353346 r356198  
    55Tested up to: 7.1
    66Requires PHP: 7.4
    7 Stable tag: 1.0.8
     7Stable tag: 1.0.9
    88License: GNU General Public License v2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    1010
    11 Classic blog theme with an orange frame and a white content area. Includes a two-column home grid with Load more, live search, a day/night color mode, Customizer options for colors, typography and logo, announcement bars, ready-made page templates (portfolio, pricing, team, FAQ, contact and more) and optional Google Fonts.
     11Classic blog theme with a two-column grid, live search, day and night mode, and page templates.
    1212
    1313== Description ==
    … …  
    5050= How do I change the logo size? =
    5151
    52 Appearance → Customize → Logo. Upload the image and set desktop and mobile max widths. Height scales automatically.
     52Appearance → Customize → Site Identity. Upload the image with the WordPress Site Logo control, then set the desktop and mobile display widths in the same section. Height scales automatically. The theme does not add a second logo uploader.
    5353
    5454= How do I make the site wider? =
    … …  
    143143
    144144== Changelog ==
     145
     146= 1.0.9 =
     147* Languages: keep bundled Catalan and Spanish only (tso-blog-ca, tso-blog-es_ES). English is the source text, so no en_US catalog.
     148* i18n: main menu label and lightbox controls use gettext. Script fallbacks are English.
     149* Announcement: the enter animation no longer plays on every page or refresh. It runs only when rotation changes to the next notice.
     150* Description rewritten to match the current theme. The readme summary stays within 150 characters.
     151* Tags checked against the WordPress.org list. All current tags are allowed. Only subject tags are limited to three; this theme uses one: blog.
     152* Theme review: the core Site Logo control stays in Site Identity. Logo width settings sit next to it. Removed the separate Logo section and the bundled logo-file fallback.
     153* Theme review: at most three Customizer textareas (footer legal text, contact intro, contact address). The Maps embed and repeater notes are single-line fields.
     154* Theme review: the pricing plans repeater accepts at most 5 plans.
     155* Theme review: screenshot.png is original theme artwork. License and source are listed under Resources.
    145156
    146157= 1.0.8 =
    … …  
    232243* Dashicons (Customizer social order handle), Copyright WordPress contributors, GNU GPL v2 or later
    233244* Google Fonts (optional remote), Copyright respective font authors, SIL Open Font License / Apache 2.0 as declared by each family on fonts.google.com
     245
     246* Screenshot (screenshot.png)
     247  Copyright: 2026 Tu Soporte Online
     248  License: GNU General Public License v2 or later
     249  License URI: https://www.gnu.org/licenses/gpl-2.0.html
     250  Source: original artwork created for this theme (orange frame, header, menu and post cards with solid-color placeholders). It does not include third-party photographs, illustrations or logos, so there is no external image URL.
  • tso-blog/1.0.9/style.css

    r353346 r356198  
    33Author: deadko
    44Author URI: https://tusoporteonline.es
    5 Description: Classic blog theme with an orange frame and a white content area. Includes a two-column home grid with Load more, live search, a day/night color mode, Customizer options for colors, typography and logo, announcement bars, ready-made page templates (portfolio, pricing, team, FAQ, contact and more) and optional Google Fonts.
    6 Version: 1.0.8
     5Description: A classic blog theme with an orange frame and a white content area. The home page is a two-column post grid with live search and a Load more button. Posts use a right sidebar. The Customizer controls colors, typography, and the Site Logo, and includes a day and night mode. Page templates are included for contact, portfolio, pricing, team, and FAQ.
     6Version: 1.0.9
    77Requires at least: 6.1
    88Tested up to: 7.1
    … …  
    747747}
    748748
     749.tsothm-announcement--slide .tsothm-announcement-slide.is-active.is-instant,
     750.tsothm-announcement--fade .tsothm-announcement-slide.is-active.is-instant,
     751.tsothm-announcement--rise .tsothm-announcement-slide.is-active.is-instant {
     752    -webkit-transition: none;
     753    transition: none;
     754    -webkit-transform: none;
     755    transform: none;
     756    opacity: 1;
     757}
     758
    749759.tsothm-announcement--slide .tsothm-announcement-slide.is-leaving {
    750760    -webkit-transform: translateX(-100%);
  • tso-blog/1.0.9/tso-customize-repeater.js

    r351940 r356198  
    2626                        html += '<div class="tso-repeater-image">';
    2727                        html += '<div class="tso-repeater-image-preview"></div>';
    28                         html += '<button type="button" class="button tso-repeater-image-select">' + ( window.tsoRepeaterL10n && window.tsoRepeaterL10n.selectImage ? window.tsoRepeaterL10n.selectImage : 'Seleccionar imagen' ) + '</button> ';
    29                         html += '<button type="button" class="button-link tso-repeater-image-remove">' + ( window.tsoRepeaterL10n && window.tsoRepeaterL10n.removeImage ? window.tsoRepeaterL10n.removeImage : 'Quitar' ) + '</button>';
     28                        html += '<button type="button" class="button tso-repeater-image-select">' + ( window.tsoRepeaterL10n && window.tsoRepeaterL10n.selectImage ? window.tsoRepeaterL10n.selectImage : 'Select image' ) + '</button> ';
     29                        html += '<button type="button" class="button-link tso-repeater-image-remove">' + ( window.tsoRepeaterL10n && window.tsoRepeaterL10n.removeImage ? window.tsoRepeaterL10n.removeImage : 'Remove' ) + '</button>';
    3030                        html += '<input type="hidden" data-field="' + escAttr( key ) + '" value="' + escAttr( value || 0 ) + '" />';
    3131                        html += '</div>';
    … …  
    7676
    7777                var fields = $el.data( 'fields' ) || [];
     78                var maxItems = parseInt( $el.attr( 'data-max' ), 10 );
     79                if ( ! maxItems || maxItems < 1 ) {
     80                        maxItems = 50;
     81                }
    7882                var $input = $el.find( '.tso-repeater-input' );
    7983                var $list = $el.find( '.tso-repeater-list' );
     84                var $add = $el.find( '.tso-repeater-add' );
    8085                var rows = [];
     86                var rawRows = $input.val() || '[]';
     87                var settingId = $input.attr( 'data-customize-setting-link' );
     88                if ( settingId && wp.customize && wp.customize( settingId ) ) {
     89                        rawRows = wp.customize( settingId ).get();
     90                }
    8191                try {
    82                         rows = JSON.parse( $input.val() || '[]' );
     92                        rows = JSON.parse( rawRows || '[]' );
    8393                } catch ( e ) {
    8494                        rows = [];
    … …  
    111121                }
    112122
     123                function updateAddButton() {
     124                        $add.prop( 'disabled', $list.children( '.tso-repeater-row' ).length >= maxItems );
     125                }
     126
    113127                function addRow( row ) {
     128                        if ( $list.children( '.tso-repeater-row' ).length >= maxItems ) {
     129                                return;
     130                        }
    114131                        row = row || {};
    115132                        var $row = $( buildRowHtml( fields, row ) );
    … …  
    120137                                }
    121138                        } );
    122                 }
    123 
    124                 rows.forEach( addRow );
     139                        updateAddButton();
     140                }
     141
     142                rows.slice( 0, maxItems ).forEach( addRow );
     143                if ( rows.length > maxItems ) {
     144                        sync();
     145                }
     146                updateAddButton();
    125147
    126148                $list.sortable( {
    … …  
    133155                $el.on( 'click', '.tso-repeater-add', function ( e ) {
    134156                        e.preventDefault();
     157                        if ( $list.children( '.tso-repeater-row' ).length >= maxItems ) {
     158                                return;
     159                        }
    135160                        addRow( {} );
    136161                        sync();
    … …  
    141166                        $( this ).closest( '.tso-repeater-row' ).remove();
    142167                        sync();
     168                        updateAddButton();
    143169                } );
    144170
    … …  
    155181
    156182                        var frame = wp.media( {
    157                                 title: window.tsoRepeaterL10n ? window.tsoRepeaterL10n.chooseImage : 'Elegir imagen',
     183                                title: window.tsoRepeaterL10n ? window.tsoRepeaterL10n.chooseImage : 'Choose image',
    158184                                multiple: false,
    159185                                library: { type: 'image' },
  • tso-blog/1.0.9/tso-lightbox.js

    r351940 r356198  
    6161    overlay.setAttribute('role', 'dialog');
    6262    overlay.setAttribute('aria-modal', 'true');
    63     overlay.setAttribute('aria-label', 'Visor de imágenes');
     63    var lbI18n = window.tsothmLightbox || {};
     64    overlay.setAttribute('aria-label', lbI18n.viewer || 'Image viewer');
    6465
    6566    var imgEl    = document.createElement('img');
    … …  
    6970    var btnClose = document.createElement('button');
    7071    btnClose.id  = 'tso-lb-close';
    71     btnClose.setAttribute('aria-label', 'Cerrar');
     72    btnClose.setAttribute('aria-label', lbI18n.close || 'Close');
    7273    btnClose.innerHTML = '&#x2715;';
    7374
    7475    var btnPrev  = document.createElement('button');
    7576    btnPrev.id   = 'tso-lb-prev';
    76     btnPrev.setAttribute('aria-label', 'Imagen anterior');
     77    btnPrev.setAttribute('aria-label', lbI18n.prev || 'Previous image');
    7778    btnPrev.innerHTML = '&#10094;';
    7879
    7980    var btnNext  = document.createElement('button');
    8081    btnNext.id   = 'tso-lb-next';
    81     btnNext.setAttribute('aria-label', 'Imagen siguiente');
     82    btnNext.setAttribute('aria-label', lbI18n.next || 'Next image');
    8283    btnNext.innerHTML = '&#10095;';
    8384
  • tso-blog/1.0.9/tso-live-search.js

    r351020 r356198  
    99        var cacheKey = ( window.tsothmSearch && window.tsothmSearch.loadMoreCacheKey ) || 'tsothm_loadmore_state';
    1010        var i18n = ( window.tsothmSearch && window.tsothmSearch.i18n ) || {};
    11         var msgSearching = i18n.searching || 'Buscando…';
    12         var msgNoResults = i18n.noResults || 'No se encontraron artículos.';
     11        var msgSearching = i18n.searching || 'Searching…';
     12        var msgNoResults = i18n.noResults || 'No posts found.';
    1313        var instances = [];
    1414
  • tso-blog/1.0.9/tso-load-more.js

    r351020 r356198  
    99        var container = document.getElementById( 'posts-container' );
    1010        var CACHE_KEY = cfg.cacheKey || 'tsothm_loadmore_state';
    11         var loadText  = cfg.loadText || 'Cargar más';
    12         var loadingText = cfg.loadingText || 'Cargando...';
     11        var loadText  = cfg.loadText || 'Load more';
     12        var loadingText = cfg.loadingText || 'Loading...';
    1313
    1414        if ( ! container ) {
  • tso-blog/1.0.9/tsothm-announcement.js

    r353345 r356198  
    4545                var timer = null;
    4646                var animating = false;
     47                var nextAt = 0;
    4748                // Keep in sync with the slowest CSS transition (~1.35s + buffer).
    4849                var animMs = reduceMotion ? 0 : 1500;
     50                var storageKey = 'tsothmAnnounceSchedule';
     51
     52                function scheduleSignature() {
     53                        return [
     54                                root.getAttribute( 'data-effect' ) || '',
     55                                String( intervalSec ),
     56                                String( slides.length )
     57                        ].join( '|' );
     58                }
     59
     60                function readSchedule() {
     61                        try {
     62                                var data = JSON.parse( window.sessionStorage.getItem( storageKey ) || '' );
     63                                if ( ! data || data.sig !== scheduleSignature() ) {
     64                                        return null;
     65                                }
     66                                if ( typeof data.index !== 'number' || typeof data.nextAt !== 'number' ) {
     67                                        return null;
     68                                }
     69                                if ( data.index < 0 || data.index >= slides.length ) {
     70                                        return null;
     71                                }
     72                                return data;
     73                        } catch ( e ) {
     74                                return null;
     75                        }
     76                }
     77
     78                function writeSchedule( slideIndex, dueAt ) {
     79                        try {
     80                                window.sessionStorage.setItem( storageKey, JSON.stringify( {
     81                                        sig: scheduleSignature(),
     82                                        index: slideIndex,
     83                                        nextAt: dueAt
     84                                } ) );
     85                        } catch ( e ) {}
     86                }
    4987
    5088                function clearSlideState( slide ) {
    51                         slide.classList.remove( 'is-active', 'is-entering', 'is-leaving' );
     89                        slide.classList.remove( 'is-active', 'is-entering', 'is-leaving', 'is-instant' );
    5290                }
    5391
    … …  
    64102                }
    65103
     104                function showSettled( slideIndex ) {
     105                        slides.forEach( clearSlideState );
     106                        slides[ slideIndex ].classList.add( 'is-active', 'is-instant' );
     107                        index = slideIndex;
     108                }
     109
    66110                function show( nextIndex ) {
    67111                        if ( animating || nextIndex === index || ! slides[ nextIndex ] ) {
    68                                 return;
     112                                return false;
    69113                        }
    70114                        animating = true;
    … …  
    72116                        var next = slides[ nextIndex ];
    73117
    74                         current.classList.remove( 'is-active', 'is-entering' );
     118                        current.classList.remove( 'is-active', 'is-entering', 'is-instant' );
    75119                        current.classList.add( 'is-leaving' );
    76120
    … …  
    78122
    79123                        window.setTimeout( function () {
    80                                 current.classList.remove( 'is-leaving' );
     124                                current.classList.remove( 'is-leaving', 'is-instant' );
    81125                                index = nextIndex;
    82126                                animating = false;
    83127                        }, animMs );
    84                 }
    85 
    86                 function next() {
    87                         show( ( index + 1 ) % slides.length );
    88                 }
    89 
    90                 function startTimer() {
     128                        return true;
     129                }
     130
     131                function queueNext() {
    91132                        if ( ! rotate || timer ) {
    92133                                return;
    93134                        }
    94                         timer = window.setInterval( next, intervalSec * 1000 );
     135                        var wait = Math.max( 0, nextAt - Date.now() );
     136                        timer = window.setTimeout( function () {
     137                                timer = null;
     138                                var dest = ( index + 1 ) % slides.length;
     139                                if ( ! show( dest ) ) {
     140                                        window.setTimeout( queueNext, Math.max( animMs, 50 ) );
     141                                        return;
     142                                }
     143                                nextAt = Date.now() + intervalSec * 1000;
     144                                writeSchedule( dest, nextAt );
     145                                queueNext();
     146                        }, wait );
    95147                }
    96148
    97149                function stopTimer() {
    98150                        if ( timer ) {
    99                                 window.clearInterval( timer );
     151                                window.clearTimeout( timer );
    100152                                timer = null;
    101153                        }
    … …  
    109161                                stopTimer();
    110162                        } else {
    111                                 startTimer();
    112                         }
    113                 }
    114 
    115                 slides.forEach( function ( slide, i ) {
    116                         clearSlideState( slide );
    117                         if ( 0 === i ) {
    118                                 if ( reduceMotion ) {
    119                                         slide.classList.add( 'is-active' );
    120                                 } else {
    121                                         playEnter( slide );
    122                                 }
    123                         }
    124                 } );
     163                                queueNext();
     164                        }
     165                }
     166
     167                slides.forEach( clearSlideState );
    125168
    126169                if ( rotate ) {
    127                         startTimer();
     170                        var now = Date.now();
     171                        var saved = readSchedule();
     172                        var period = intervalSec * 1000;
     173                        if ( saved && now < saved.nextAt ) {
     174                                index = saved.index;
     175                                nextAt = saved.nextAt;
     176                        } else if ( saved ) {
     177                                var steps = Math.floor( ( now - saved.nextAt ) / period ) + 1;
     178                                index = ( saved.index + steps ) % slides.length;
     179                                var overdue = ( now - saved.nextAt ) % period;
     180                                nextAt = now + ( overdue ? period - overdue : period );
     181                        } else {
     182                                index = 0;
     183                                nextAt = now + period;
     184                        }
     185                        showSettled( index );
     186                        writeSchedule( index, nextAt );
     187                        queueNext();
     188                } else {
     189                        showSettled( 0 );
    128190                }
    129191
Note: See TracChangeset for help on using the changeset viewer.