Как неудобство стандартных плагинов привело меня к созданию идеального конструктора таблиц для WordPress

Скришот плагина

КАК Я ПЕРЕСТАЛ МУЧАТЬСЯ С ТАБЛИЦАМИ В WORDPRESS И СДЕЛАЛ ИДЕАЛЬНЫЙ ПЛАГИН ЗА ВЕЧЕР
Знакомо? Сидишь, перебираешь десятки плагинов для таблиц — один слишком тяжёлый, второй не адаптивный, третий вообще платный, а в бесплатной версии даже цвет ячейки не поменять. Я через это прошёл. И знаете что? Я сделал свой плагин. И он оказался лучше всех, что я пробовал.

ПОЧЕМУ ВСЕ ПЛАГИНЫ — ЭТО КОМПРОМИСС?
Каждый плагин для таблиц в WordPress — это компромисс. Где-то интерфейс как из 2010 года, где-то таблицы ломаются на мобилках, а где-то производительность падает до 20 баллов в PageSpeed.

Мне нужно было простое:

Красивые цветные таблицы

Адаптив под мобильные

Удобный конструктор без коротких кодов

И чтобы сайт не тормозил

Я искал месяц. Не нашёл. Решил — напишу сам.

Я НЕ РАЗРАБОТЧИК, НО У МЕНЯ БЫЛ DeepSeek
Я знаю WordPress как пользователь. С кодом на «вы». PHP для меня — тёмный лес. Поэтому я пошёл к DeepSeek и сказал:

«Вот что я хочу. Помоги сделать работающий плагин».

И DeepSeek стал моим техническим напарником. Мы работали так:

Я описывал интерфейс — кнопки, цвета, поля.

DeepSeek писал код и объяснял, что делает каждая строка.

Я тестировал — находил баги, DeepSeek исправлял.

Я просил улучшения — DeepSeek добавлял фичи.

Весь процесс занял один вечер. А теперь у меня есть плагин, который делает ровно то, что мне нужно.

ЧТО УМЕЕТ МОЙ ПЛАГИН СЕЙЧАС?
Вот список возможностей, которых мне не хватало во всех существующих решениях:

Функция Описание
🎨 5 стилей оформления Градиентный, современный, элегантный, яркий, минимализм
📱 Адаптивность На мобильных таблица превращается в карточки
🖌️ Цвета для каждой ячейки Фон и текст настраиваются индивидуально
➕➖ Добавление/удаление Колонки и строки — в один клик
👁️ Предпросмотр Перед сохранением видите, как будет выглядеть таблица
📋 Копирование шорткода Один клик — и шорткод у вас в буфере
📤 Экспорт/импорт Таблицы можно сохранять в JSON и переносить на другие сайты
🌀 Клонирование Создаёте копию таблицы за секунду
🔍 Поиск по таблицам Быстро находите нужную среди десятков
📄 Пагинация Список таблиц разбит на страницы, если их много
⚡ Оптимизация Кэширование, легковесный CSS, 90+ баллов PageSpeed
КАК ЭТО ВЫГЛЯДИТ В ИНТЕРФЕЙСЕ
Всё максимально просто:

Заходите в админку → новый пункт меню «Colorful Tables Pro»

Вводите название таблицы, выбираете стиль

Указываете количество колонок и строк (кнопками +/−)

Заполняете заголовки и ячейки — можно менять цвета

Нажимаете «Предпросмотр» — видите, как будет на сайте

Сохраняете — получаете шорткод

Колонка 1
Колонка 1

Всё. Никаких танцев с бубном.

ВОТ КОД ПЛАГИНА (исправленная и улучшенная версия)
Я переписал плагин, добавил новые фичи и исправил все баги. Теперь он ещё лучше:





<?php
/**
 * Plugin Name: Colorful Tables Pro
 * Description: Продвинутый конструктор таблиц с управлением колонками, строками, экспортом/импортом и клонированием
 * Version: 2.0.0
 * Author: Your Name
 * Text Domain: colorful-tables-pro
 */

if (!defined('ABSPATH')) {
    exit;
}

class ColorfulTablesPro {
    
    private $storage_key = 'colorful_tables_pro_data';
    private $max_columns = 10;
    private $max_rows = 30;
    private $tables_per_page = 10;
    
    public function __construct() {
        add_action('init', array($this, 'init'));
        add_action('admin_menu', array($this, 'admin_menu'));
        add_shortcode('colorful_table', array($this, 'shortcode'));
        add_action('admin_init', array($this, 'check_compatibility'));
        add_action('wp_ajax_ct_export_table', array($this, 'ajax_export_table'));
        add_action('wp_ajax_ct_import_table', array($this, 'ajax_import_table'));
        add_action('wp_ajax_ct_clone_table', array($this, 'ajax_clone_table'));
        
        register_activation_hook(__FILE__, array($this, 'activate'));
        register_deactivation_hook(__FILE__, array($this, 'deactivate'));
    }
    
    public function activate() {
        if (!current_user_can('activate_plugins')) {
            return;
        }
        if (empty(get_option($this->storage_key))) {
            update_option($this->storage_key, array());
        }
    }
    
    public function deactivate() {
        // Clean up if needed
    }
    
    public function check_compatibility() {
        if (version_compare(get_bloginfo('version'), '5.0', '<')) {
            deactivate_plugins(plugin_basename(__FILE__));
            wp_die(__('Плагин требует WordPress версии 5.0 или выше', 'colorful-tables-pro'));
        }
    }
    
    public function init() {
        add_action('wp_enqueue_scripts', array($this, 'frontend_assets'));
        add_action('admin_enqueue_scripts', array($this, 'admin_assets'));
        load_plugin_textdomain('colorful-tables-pro', false, dirname(plugin_basename(__FILE__)) . '/languages');
    }
    
    public function admin_menu() {
        add_menu_page(
            __('Colorful Tables Pro', 'colorful-tables-pro'),
            __('Colorful Tables Pro', 'colorful-tables-pro'),
            'manage_options',
            'colorful-tables-pro',
            array($this, 'admin_page'),
            'dashicons-art',
            100
        );
    }
    
    public function frontend_assets() {
        wp_add_inline_style('wp-block-library', $this->get_frontend_css());
    }
    
    public function admin_assets($hook) {
        if ($hook !== 'toplevel_page_colorful-tables-pro') {
            return;
        }
        wp_enqueue_script('jquery');
        add_action('admin_head', array($this, 'output_admin_css'));
    }
    
    public function output_admin_css() {
        echo '<style>' . $this->get_admin_css() . '</style>';
    }
    
    public function admin_page() {
        if (!current_user_can('manage_options')) {
            wp_die(__('Недостаточно прав для доступа к этой странице.', 'colorful-tables-pro'));
        }
        
        // Process forms
        if (isset($_POST['save_table']) && check_admin_referer('colorful_tables_nonce')) {
            $this->save_table();
            echo '<div class="notice notice-success is-dismissible"><p>&#x2705; ' . __('Таблица сохранена успешно!', 'colorful-tables-pro') . '</p></div>';
        }
        
        if (isset($_POST['delete_table']) && check_admin_referer('colorful_tables_nonce')) {
            $this->delete_table();
            echo '<div class="notice notice-success is-dismissible"><p>&#x1f5d1;&#xfe0f; ' . __('Таблица удалена!', 'colorful-tables-pro') . '</p></div>';
        }
        
        $tables = get_option($this->storage_key, array());
        $edit_table_data = null;
        $editing_table_id = null;
        
        if (isset($_GET['edit_table'])) {
            $editing_table_id = intval($_GET['edit_table']);
            if (isset($tables[$editing_table_id])) {
                $edit_table_data = $tables[$editing_table_id];
            }
        }
        
        // Pagination
        $current_page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
        $total_tables = count($tables);
        $total_pages = ceil($total_tables / $this->tables_per_page);
        $offset = ($current_page - 1) * $this->tables_per_page;
        $paged_tables = array_slice($tables, $offset, $this->tables_per_page, true);
        
        $edit_data_js = $edit_table_data ? $this->safe_json_encode($edit_table_data) : 'null';
        ?>
        
        <div class="wrap ct-wrapper <?php echo $editing_table_id ? 'ct-editing' : ''; ?>">
            <div class="ct-header">
                <div class="ct-header-content">
                    <h1>
                        <span class="ct-logo">&#x1f3a8;</span>
                        <?php _e('Colorful Tables Pro', 'colorful-tables-pro'); ?>
                        <?php if ($editing_table_id): ?>
                            <span class="ct-badge"><?php _e('Режим редактирования', 'colorful-tables-pro'); ?></span>
                        <?php endif; ?>
                    </h1>
                    <p class="ct-subtitle"><?php _e('Продвинутый конструктор таблиц с полным контролем над колонками, строками и стилями', 'colorful-tables-pro'); ?></p>
                </div>
            </div>

            <div class="ct-container">
                <div class="ct-main-panel">
                    <div class="ct-panel ct-builder-panel">
                        <div class="ct-panel-header">
                            <h2>
                                <?php if ($editing_table_id): ?>
                                    <span class="ct-icon">&#x270f;&#xfe0f;</span> <?php _e('Редактирование таблицы', 'colorful-tables-pro'); ?>
                                <?php else: ?>
                                    <span class="ct-icon">&#x2795;</span> <?php _e('Создать новую таблицу', 'colorful-tables-pro'); ?>
                                <?php endif; ?>
                            </h2>
                        </div>

                        <form method="post" id="ct-table-form">
                            <?php wp_nonce_field('colorful_tables_nonce'); ?>
                            
                            <?php if ($editing_table_id): ?>
                                <input type="hidden" name="edit_table_id" value="<?php echo esc_attr($editing_table_id); ?>">
                            <?php endif; ?>

                            <div class="ct-section">
                                <div class="ct-section-header">
                                    <h3><span class="ct-icon">&#x2699;&#xfe0f;</span> <?php _e('Основные настройки', 'colorful-tables-pro'); ?></h3>
                                </div>
                                <div class="ct-section-content">
                                    <div class="ct-form-grid-2">
                                        <div class="ct-form-group">
                                            <label class="ct-label"><?php _e('Название таблицы', 'colorful-tables-pro'); ?></label>
                                            <input type="text" name="table_name" 
                                                   value="<?php echo $edit_table_data ? esc_attr($edit_table_data['name']) : __('Моя продвинутая таблица', 'colorful-tables-pro'); ?>" 
                                                   class="ct-input" required>
                                        </div>
                                        <div class="ct-form-group">
                                            <label class="ct-label"><?php _e('Стиль оформления', 'colorful-tables-pro'); ?></label>
                                            <select name="style" class="ct-select" id="ct-style-select">
                                                <option value="gradient" <?php selected('gradient', $edit_table_data ? $edit_table_data['style'] : 'gradient'); ?>><?php _e('Градиентный', 'colorful-tables-pro'); ?></option>
                                                <option value="modern" <?php selected('modern', $edit_table_data ? $edit_table_data['style'] : ''); ?>><?php _e('Современный', 'colorful-tables-pro'); ?></option>
                                                <option value="elegant" <?php selected('elegant', $edit_table_data ? $edit_table_data['style'] : ''); ?>><?php _e('Элегантный', 'colorful-tables-pro'); ?></option>
                                                <option value="vibrant" <?php selected('vibrant', $edit_table_data ? $edit_table_data['style'] : ''); ?>><?php _e('Яркий', 'colorful-tables-pro'); ?></option>
                                                <option value="minimal" <?php selected('minimal', $edit_table_data ? $edit_table_data['style'] : ''); ?>><?php _e('Минимализм', 'colorful-tables-pro'); ?></option>
                                            </select>
                                        </div>
                                    </div>
                                </div>
                            </div>

                            <div class="ct-section">
                                <div class="ct-section-header">
                                    <h3><span class="ct-icon">&#x1f4d0;</span> <?php _e('Структура таблицы', 'colorful-tables-pro'); ?></h3>
                                </div>
                                <div class="ct-section-content">
                                    <div class="ct-structure-grid">
                                        <div class="ct-structure-item">
                                            <h4><?php _e('Колонки', 'colorful-tables-pro'); ?></h4>
                                            <div class="ct-control-group">
                                                <div class="ct-number-control">
                                                    <label><?php _e('Количество:', 'colorful-tables-pro'); ?></label>
                                                    <div class="ct-number-buttons">
                                                        <button type="button" class="ct-number-btn" data-action="decrease" data-target="columns">−</button>
                                                        <input type="number" name="columns" id="ct-columns" 
                                                               value="<?php echo $edit_table_data ? $edit_table_data['columns'] : 3; ?>" 
                                                               min="1" max="<?php echo $this->max_columns; ?>" class="ct-number-input" readonly>
                                                        <button type="button" class="ct-number-btn" data-action="increase" data-target="columns">+</button>
                                                    </div>
                                                </div>
                                                <div class="ct-action-buttons-grid">
                                                    <button type="button" class="button ct-button-small" id="ct-add-column">&#x2795; <?php _e('Добавить', 'colorful-tables-pro'); ?></button>
                                                    <button type="button" class="button ct-button-small ct-button-danger" id="ct-remove-column">&#x2796; <?php _e('Удалить', 'colorful-tables-pro'); ?></button>
                                                </div>
                                            </div>
                                        </div>
                                        <div class="ct-structure-item">
                                            <h4><?php _e('Строки', 'colorful-tables-pro'); ?></h4>
                                            <div class="ct-control-group">
                                                <div class="ct-number-control">
                                                    <label><?php _e('Количество:', 'colorful-tables-pro'); ?></label>
                                                    <div class="ct-number-buttons">
                                                        <button type="button" class="ct-number-btn" data-action="decrease" data-target="rows">−</button>
                                                        <input type="number" name="rows" id="ct-rows" 
                                                               value="<?php echo $edit_table_data ? count($edit_table_data['rows']) : 5; ?>" 
                                                               min="1" max="<?php echo $this->max_rows; ?>" class="ct-number-input" readonly>
                                                        <button type="button" class="ct-number-btn" data-action="increase" data-target="rows">+</button>
                                                    </div>
                                                </div>
                                                <div class="ct-action-buttons-grid">
                                                    <button type="button" class="button ct-button-small" id="ct-add-row">&#x2795; <?php _e('Добавить', 'colorful-tables-pro'); ?></button>
                                                    <button type="button" class="button ct-button-small ct-button-danger" id="ct-remove-row">&#x2796; <?php _e('Удалить', 'colorful-tables-pro'); ?></button>
                                                </div>
                                            </div>
                                        </div>
                                    </div>
                                    <div class="ct-structure-info" style="display: none;">
                                        <div class="ct-structure-stat"></div>
                                    </div>
                                </div>
                            </div>

                            <div class="ct-section">
                                <div class="ct-section-header">
                                    <h3><span class="ct-icon">&#x1f4cb;</span> <?php _e('Заголовки таблицы', 'colorful-tables-pro'); ?></h3>
                                    <div class="ct-color-presets-grid">
                                        <span><?php _e('Быстрые цвета:', 'colorful-tables-pro'); ?></span>
                                        <button type="button" class="ct-color-swatch" data-bg="#3498db" data-text="#ffffff" style="background: #3498db;"></button>
                                        <button type="button" class="ct-color-swatch" data-bg="#2ecc71" data-text="#ffffff" style="background: #2ecc71;"></button>
                                        <button type="button" class="ct-color-swatch" data-bg="#e74c3c" data-text="#ffffff" style="background: #e74c3c;"></button>
                                        <button type="button" class="ct-color-swatch" data-bg="#f39c12" data-text="#ffffff" style="background: #f39c12;"></button>
                                        <button type="button" class="ct-color-swatch" data-bg="#9b59b6" data-text="#ffffff" style="background: #9b59b6;"></button>
                                    </div>
                                </div>
                                <div class="ct-section-content">
                                    <div id="ct-headers-container" class="ct-headers-grid"></div>
                                </div>
                            </div>

                            <div class="ct-section">
                                <div class="ct-section-header">
                                    <h3><span class="ct-icon">&#x1f4ca;</span> <?php _e('Данные таблицы', 'colorful-tables-pro'); ?></h3>
                                    <div class="ct-table-actions-grid">
                                        <div class="ct-color-presets-grid">
                                            <span><?php _e('Цвета ячеек:', 'colorful-tables-pro'); ?></span>
                                            <button type="button" class="ct-color-swatch" data-bg="#ecf0f1" data-text="#2c3e50" style="background: #ecf0f1;"></button>
                                            <button type="button" class="ct-color-swatch" data-bg="#fff9e6" data-text="#e67e22" style="background: #fff9e6;"></button>
                                            <button type="button" class="ct-color-swatch" data-bg="#e8f6f3" data-text="#27ae60" style="background: #e8f6f3;"></button>
                                        </div>
                                        <button type="button" class="button ct-button-small" id="ct-clear-all">&#x1f9f9; <?php _e('Очистить все', 'colorful-tables-pro'); ?></button>
                                    </div>
                                </div>
                                <div class="ct-section-content">
                                    <div id="ct-rows-container" class="ct-rows-container"></div>
                                </div>
                            </div>

                            <div class="ct-actions">
                                <div class="ct-main-actions-grid">
                                    <button type="button" class="button ct-button-secondary" id="ct-preview">&#x1f441;&#xfe0f; <?php _e('Предпросмотр', 'colorful-tables-pro'); ?></button>
                                    <?php if ($editing_table_id): ?>
                                        <a href="<?php echo admin_url('admin.php?page=colorful-tables-pro'); ?>" class="button">&#x21a9;&#xfe0f; <?php _e('Отмена', 'colorful-tables-pro'); ?></a>
                                    <?php endif; ?>
                                    <button type="submit" name="save_table" class="button button-primary ct-button-primary">
                                        &#x1f4be; <?php echo $editing_table_id ? __('Обновить таблицу', 'colorful-tables-pro') : __('Сохранить таблицу', 'colorful-tables-pro'); ?>
                                    </button>
                                </div>
                            </div>
                        </form>
                    </div>
                </div>

                <div class="ct-sidebar">
                    <div class="ct-panel ct-tables-panel">
                        <div class="ct-panel-header">
                            <h3><span class="ct-icon">&#x1f4da;</span> <?php _e('Мои таблицы', 'colorful-tables-pro'); ?></h3>
                            <div class="ct-panel-actions">
                                <input type="text" id="ct-table-search" placeholder="<?php _e('Поиск...', 'colorful-tables-pro'); ?>" class="ct-input ct-search-input">
                            </div>
                        </div>
                        <div class="ct-panel-content">
                            <?php if (empty($tables)): ?>
                                <div class="ct-empty-state">
                                    <div class="ct-empty-icon">&#x1f4ed;</div>
                                    <p><?php _e('Пока нет созданных таблиц', 'colorful-tables-pro'); ?></p>
                                    <p class="ct-empty-hint"><?php _e('Создайте свою первую таблицу!', 'colorful-tables-pro'); ?></p>
                                </div>
                            <?php else: ?>
                                <div class="ct-tables-grid" id="ct-tables-list">
                                    <?php foreach ($paged_tables as $id => $table): ?>
                                        <div class="ct-table-card" data-id="<?php echo $id; ?>" data-name="<?php echo esc_attr($table['name']); ?>">
                                            <div class="ct-table-header-grid">
                                                <h4><?php echo esc_html($table['name']); ?></h4>
                                                <span class="ct-table-badge">#<?php echo $id; ?></span>
                                            </div>
                                            <div class="ct-table-meta-grid">
                                                <span class="ct-meta-item">&#x1f4cf; <?php echo $table['columns']; ?>×<?php echo count($table['rows']); ?></span>
                                                <span class="ct-meta-item">&#x1f3a8; <?php echo $this->get_style_name($table['style']); ?></span>
                                            </div>
                                            <div class="ct-table-shortcode-grid">
                                                <code>

Неверный ID таблицы

</code> <button type="button" class="ct-copy-btn" data-shortcode='

Неверный ID таблицы

'>&#x1f4cb;</button> </div> <div class="ct-table-actions-grid"> <a href="<?php echo admin_url('admin.php?page=colorful-tables-pro&edit_table=' . $id); ?>" class="button ct-button-small">&#x270f;&#xfe0f; <?php _e('Редактировать', 'colorful-tables-pro'); ?></a> <button type="button" class="button ct-button-small ct-clone-btn" data-id="<?php echo $id; ?>">&#x1f504; <?php _e('Клонировать', 'colorful-tables-pro'); ?></button> <button type="button" class="button ct-button-small ct-export-btn" data-id="<?php echo $id; ?>">&#x1f4e4;</button> <form method="post" class="ct-delete-form" style="display:inline;"> <?php wp_nonce_field('colorful_tables_nonce'); ?> <input type="hidden" name="delete_table" value="<?php echo $id; ?>"> <button type="submit" class="button ct-button-small ct-button-danger" onclick="return confirm('<?php _e('Удалить таблицу?', 'colorful-tables-pro'); ?>')">&#x1f5d1;&#xfe0f;</button> </form> </div> </div> <?php endforeach; ?> </div> <?php if ($total_pages > 1): ?> <div class="ct-pagination"> <?php echo paginate_links(array( 'base' => add_query_arg('paged', '%#%'), 'format' => '', 'prev_text' => '«', 'next_text' => '»', 'total' => $total_pages, 'current' => $current_page )); ?> </div> <?php endif; ?> <?php endif; ?> <div class="ct-import-area"> <h4><?php _e('Импорт таблицы', 'colorful-tables-pro'); ?></h4> <input type="file" id="ct-import-file" accept=".json"> <button type="button" class="button ct-button-small" id="ct-import-btn">&#x1f4e5; <?php _e('Импортировать', 'colorful-tables-pro'); ?></button> <div id="ct-import-status"></div> </div> </div> </div> <div class="ct-panel ct-stats-panel"> <div class="ct-panel-header"> <h3><span class="ct-icon">&#x1f4ca;</span> <?php _e('Статистика', 'colorful-tables-pro'); ?></h3> </div> <div class="ct-panel-content"> <div class="ct-stats-grid"> <div class="ct-stat-item"> <div class="ct-stat-number"><?php echo count($tables); ?></div> <div class="ct-stat-label"><?php _e('Всего таблиц', 'colorful-tables-pro'); ?></div> </div> <div class="ct-stat-item"> <div class="ct-stat-number"> <?php $total_cells = 0; foreach ($tables as $table) { $total_cells += $table['columns'] * count($table['rows']); } echo $total_cells; ?> </div> <div class="ct-stat-label"><?php _e('Всего ячеек', 'colorful-tables-pro'); ?></div> </div> </div> </div> </div> </div> </div> </div> <div id="ct-preview-modal" class="ct-modal" style="display: none;"> <div class="ct-modal-content"> <div class="ct-modal-header-grid"> <h3>&#x1f441;&#xfe0f; <?php _e('Предпросмотр таблицы', 'colorful-tables-pro'); ?></h3> <button type="button" class="ct-modal-close">&times;</button> </div> <div class="ct-modal-body"> <div id="ct-preview-content"></div> </div> </div> </div> <script> jQuery(document).ready(function($) { var editTableData = <?php echo $edit_data_js; ?>; var maxColumns = <?php echo $this->max_columns; ?>; var maxRows = <?php echo $this->max_rows; ?>; initTableBuilder(); $(document).on('click', '.ct-number-btn', handleNumberInput); $(document).on('click', '#ct-add-column', addColumn); $(document).on('click', '#ct-remove-column', removeColumn); $(document).on('click', '#ct-add-row', addRow); $(document).on('click', '#ct-remove-row', removeRow); $(document).on('click', '#ct-clear-all', clearAllCells); $(document).on('click', '.ct-color-swatch', applyColorPreset); $(document).on('click', '#ct-preview', showPreview); $(document).on('click', '.ct-modal-close', closePreview); $(document).on('click', '.ct-copy-btn', copyShortcode); $(document).on('click', '.ct-clear-row', clearRow); $(document).on('click', '.ct-clone-btn', cloneTable); $(document).on('click', '.ct-export-btn', exportTable); $(document).on('click', '#ct-import-btn', importTable); $(document).on('keyup', '#ct-table-search', searchTables); function initTableBuilder() { generateHeaders(); generateRows(); updateStructureInfo(); initColorInputs(); } function generateHeaders() { var columns = parseInt($('#ct-columns').val()); var container = $('#ct-headers-container'); var html = ''; for (var i = 0; i < columns; i++) { var headerData = getHeaderData(i); html += ` <div class="ct-header-card" data-column="${i}"> <div class="ct-card-header-grid"> <label><?php _e('Колонка', 'colorful-tables-pro'); ?> ${i+1}</label> <div class="ct-color-preview" style="background: ${headerData.bg_color}"></div> </div> <input type="text" class="ct-input" name="headers[${i}][content]" value="${headerData.content}" placeholder="<?php _e('Заголовок колонки...', 'colorful-tables-pro'); ?>"> <div class="ct-color-controls-grid"> <div class="ct-color-group-grid"> <label><?php _e('Фон', 'colorful-tables-pro'); ?></label> <div class="ct-color-input-grid"> <input type="color" class="ct-color-input" name="headers[${i}][bg_color]" value="${headerData.bg_color}"> <input type="text" class="ct-color-hex" value="${headerData.bg_color}" placeholder="#3498db"> </div> </div> <div class="ct-color-group-grid"> <label><?php _e('Текст', 'colorful-tables-pro'); ?></label> <div class="ct-color-input-grid"> <input type="color" class="ct-color-input" name="headers[${i}][text_color]" value="${headerData.text_color}"> <input type="text" class="ct-color-hex" value="${headerData.text_color}" placeholder="#ffffff"> </div> </div> </div> </div> `; } container.html(html); initColorInputs(); } function generateRows() { var columns = parseInt($('#ct-columns').val()); var rows = parseInt($('#ct-rows').val()); var container = $('#ct-rows-container'); var html = ''; for (var r = 0; r < rows; r++) { html += `<div class="ct-row-card" data-row="${r}"> <div class="ct-row-header-grid"> <span><?php _e('Строка', 'colorful-tables-pro'); ?> ${r+1}</span> <button type="button" class="ct-clear-row" data-row="${r}">&#x1f9f9; <?php _e('Очистить', 'colorful-tables-pro'); ?></button> </div> <div class="ct-cells-grid">`; for (var c = 0; c < columns; c++) { var cellData = getCellData(r, c); html += ` <div class="ct-cell-card" data-row="${r}" data-col="${c}"> <div class="ct-cell-header-grid"> <label>${r+1}-${c+1}</label> <div class="ct-color-preview" style="background: ${cellData.bg_color || '#ffffff'}"></div> </div> <textarea name="rows[${r}][cells][${c}][content]" placeholder="<?php _e('Введите данные...', 'colorful-tables-pro'); ?>" rows="2">${cellData.content}</textarea> <div class="ct-color-controls-grid"> <div class="ct-color-group-grid"> <label><?php _e('Фон', 'colorful-tables-pro'); ?></label> <div class="ct-color-input-grid"> <input type="color" class="ct-color-input" name="rows[${r}][cells][${c}][bg_color]" value="${cellData.bg_color || '#ffffff'}"> <input type="text" class="ct-color-hex" value="${cellData.bg_color || '#ffffff'}" placeholder="#ffffff"> </div> </div> <div class="ct-color-group-grid"> <label><?php _e('Текст', 'colorful-tables-pro'); ?></label> <div class="ct-color-input-grid"> <input type="color" class="ct-color-input" name="rows[${r}][cells][${c}][text_color]" value="${cellData.text_color || '#000000'}"> <input type="text" class="ct-color-hex" value="${cellData.text_color || '#000000'}" placeholder="#000000"> </div> </div> </div> </div> `; } html += `</div></div>`; } container.html(html); initColorInputs(); } function getHeaderData(index) { if (editTableData && editTableData.headers && editTableData.headers[index]) { return editTableData.headers[index]; } var defaultColors = [ {bg: '#3498db', text: '#ffffff'}, {bg: '#2ecc71', text: '#ffffff'}, {bg: '#e74c3c', text: '#ffffff'}, {bg: '#f39c12', text: '#ffffff'}, {bg: '#9b59b6', text: '#ffffff'} ]; var color = defaultColors[index] || defaultColors[0]; return { content: '<?php _e('Колонка', 'colorful-tables-pro'); ?> ' + (index + 1), bg_color: color.bg, text_color: color.text }; } function getCellData(row, col) { if (editTableData && editTableData.rows && editTableData.rows[row] && editTableData.rows[row].cells && editTableData.rows[row].cells[col]) { return editTableData.rows[row].cells[col]; } return { content: '', bg_color: '#ffffff', text_color: '#000000' }; } function handleNumberInput() { var $btn = $(this); var action = $btn.data('action'); var target = $btn.data('target'); var $input = $('#ct-' + target); var current = parseInt($input.val()); if (action === 'increase') { if ((target === 'columns' && current < maxColumns) || (target === 'rows' && current < maxRows)) { $input.val(current + 1); updateTable(); } } else if (action === 'decrease') { if (current > 1) { $input.val(current - 1); updateTable(); } } } function updateTable() { generateHeaders(); generateRows(); updateStructureInfo(); } function addColumn() { var $input = $('#ct-columns'); var current = parseInt($input.val()); if (current < maxColumns) { $input.val(current + 1); updateTable(); showNotification('<?php _e('Колонка добавлена', 'colorful-tables-pro'); ?>'); } else { showNotification('<?php printf(__('Максимум %d колонок', 'colorful-tables-pro'), $this->max_columns); ?>', 'error'); } } function removeColumn() { var $input = $('#ct-columns'); var current = parseInt($input.val()); if (current > 1) { $input.val(current - 1); updateTable(); showNotification('<?php _e('Колонка удалена', 'colorful-tables-pro'); ?>'); } else { showNotification('<?php _e('Минимум 1 колонка', 'colorful-tables-pro'); ?>', 'error'); } } function addRow() { var $input = $('#ct-rows'); var current = parseInt($input.val()); if (current < maxRows) { $input.val(current + 1); updateTable(); showNotification('<?php _e('Строка добавлена', 'colorful-tables-pro'); ?>'); } else { showNotification('<?php printf(__('Максимум %d строк', 'colorful-tables-pro'), $this->max_rows); ?>', 'error'); } } function removeRow() { var $input = $('#ct-rows'); var current = parseInt($input.val()); if (current > 1) { $input.val(current - 1); updateTable(); showNotification('<?php _e('Строка удалена', 'colorful-tables-pro'); ?>'); } else { showNotification('<?php _e('Минимум 1 строка', 'colorful-tables-pro'); ?>', 'error'); } } function clearAllCells() { if (confirm('<?php _e('Очистить все ячейки?', 'colorful-tables-pro'); ?>')) { $('.ct-cell-card textarea').val(''); showNotification('<?php _e('Все ячейки очищены', 'colorful-tables-pro'); ?>'); } } function clearRow() { var row = $(this).data('row'); $(`.ct-cell-card[data-row="${row}"] textarea`).val(''); showNotification('<?php _e('Строка очищена', 'colorful-tables-pro'); ?>'); } function applyColorPreset() { var $swatch = $(this); var bgColor = $swatch.data('bg'); var textColor = $swatch.data('text'); var isHeader = $swatch.closest('.ct-section-header').length > 0; if (isHeader) { $('.ct-header-card .ct-color-input[name$="[bg_color]"]').val(bgColor).trigger('change'); $('.ct-header-card .ct-color-input[name$="[text_color]"]').val(textColor).trigger('change'); showNotification('<?php _e('Цвета заголовков применены', 'colorful-tables-pro'); ?>'); } else { $('.ct-cell-card .ct-color-input[name$="[bg_color]"]').val(bgColor).trigger('change'); $('.ct-cell-card .ct-color-input[name$="[text_color]"]').val(textColor).trigger('change'); showNotification('<?php _e('Цвета ячеек применены', 'colorful-tables-pro'); ?>'); } } function initColorInputs() { $('.ct-color-input').off('change').on('change', function() { var $hex = $(this).siblings('.ct-color-hex'); $hex.val($(this).val()); updateColorPreview($(this)); }); $('.ct-color-hex').off('input').on('input', function() { var $color = $(this).siblings('.ct-color-input'); var value = $(this).val(); if (/^#[0-9A-F]{6}$/i.test(value)) { $color.val(value); updateColorPreview($color); } }); $('.ct-color-input').each(function() { updateColorPreview($(this)); }); } function updateColorPreview($input) { var color = $input.val(); var $preview = $input.closest('.ct-color-controls-grid').siblings('.ct-card-header-grid').find('.ct-color-preview'); if ($preview.length === 0) { $preview = $input.closest('.ct-color-controls-grid').siblings('.ct-cell-header-grid').find('.ct-color-preview'); } $preview.css('background-color', color); } function updateStructureInfo() { var columns = $('#ct-columns').val(); var rows = $('#ct-rows').val(); var totalCells = columns * rows; $('.ct-structure-info').show().find('.ct-structure-stat').html( '<strong><?php _e('Размер:', 'colorful-tables-pro'); ?></strong> ' + columns + ' × ' + rows + ' = ' + totalCells + ' <?php _e('ячеек', 'colorful-tables-pro'); ?>' ); } function showPreview() { var columns = $('#ct-columns').val(); var style = $('#ct-style-select').val(); var headers = []; $('.ct-header-card').each(function() { headers.push({ content: $(this).find('input[type="text"]').val(), bg_color: $(this).find('.ct-color-input[name$="[bg_color]"]').val(), text_color: $(this).find('.ct-color-input[name$="[text_color]"]').val() }); }); var tableRows = []; $('.ct-row-card').each(function() { var cells = []; $(this).find('.ct-cell-card').each(function() { cells.push({ content: $(this).find('textarea').val(), bg_color: $(this).find('.ct-color-input[name$="[bg_color]"]').val(), text_color: $(this).find('.ct-color-input[name$="[text_color]"]').val() }); }); tableRows.push({ cells: cells }); }); var previewHtml = '<div class="ct-table-container style-' + style + '">'; previewHtml += '<div class="ct-table-desktop" style="grid-template-columns: repeat(' + columns + ', 1fr)">'; headers.forEach(function(header) { var headerStyle = ''; if (header.bg_color) headerStyle += 'background-color: ' + header.bg_color + ';'; if (header.text_color) headerStyle += 'color: ' + header.text_color + ';'; previewHtml += '<div class="ct-table-header" style="' + headerStyle + '">' + (header.content || '<?php _e('Заголовок', 'colorful-tables-pro'); ?>') + '</div>'; }); tableRows.forEach(function(row) { row.cells.forEach(function(cell) { var cellStyle = ''; if (cell.bg_color) cellStyle += 'background-color: ' + cell.bg_color + ';'; if (cell.text_color) cellStyle += 'color: ' + cell.text_color + ';'; previewHtml += '<div class="ct-table-cell" style="' + cellStyle + '">' + (cell.content || '<?php _e('Данные', 'colorful-tables-pro'); ?>') + '</div>'; }); }); previewHtml += '</div>'; previewHtml += '<div class="ct-table-mobile">'; tableRows.forEach(function(row) { previewHtml += '<div class="ct-mobile-row">'; row.cells.forEach(function(cell, cellIndex) { if (headers[cellIndex]) { var cellStyle = ''; if (cell.bg_color) cellStyle += 'background-color: ' + cell.bg_color + ';'; if (cell.text_color) cellStyle += 'color: ' + cell.text_color + ';'; previewHtml += '<div class="ct-mobile-cell" style="' + cellStyle + '">'; previewHtml += '<div class="ct-mobile-header">' + (headers[cellIndex].content || '<?php _e('Заголовок', 'colorful-tables-pro'); ?>') + '</div>'; previewHtml += '<div class="ct-mobile-content">' + (cell.content || '<?php _e('Данные', 'colorful-tables-pro'); ?>') + '</div>'; previewHtml += '</div>'; } }); previewHtml += '</div>'; }); previewHtml += '</div></div>'; $('#ct-preview-content').html(previewHtml); $('#ct-preview-modal').show(); } function closePreview() { $('#ct-preview-modal').hide(); } function copyShortcode() { var shortcode = $(this).data('shortcode'); navigator.clipboard.writeText(shortcode).then(function() { var $btn = $(this); var originalHtml = $btn.html(); $btn.html('&#x2705;'); setTimeout(function() { $btn.html(originalHtml); }, 2000); showNotification('<?php _e('Шорткод скопирован', 'colorful-tables-pro'); ?>'); }.bind(this)); } function cloneTable() { var id = $(this).data('id'); $.post(ajaxurl, { action: 'ct_clone_table', nonce: '<?php echo wp_create_nonce('ct_ajax_nonce'); ?>', id: id }, function(response) { if (response.success) { location.reload(); } else { showNotification(response.data || '<?php _e('Ошибка клонирования', 'colorful-tables-pro'); ?>', 'error'); } }); } function exportTable() { var id = $(this).data('id'); $.post(ajaxurl, { action: 'ct_export_table', nonce: '<?php echo wp_create_nonce('ct_ajax_nonce'); ?>', id: id }, function(response) { if (response.success) { var blob = new Blob([JSON.stringify(response.data, null, 2)], {type: 'application/json'}); var link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = 'table-' + id + '.json'; link.click(); showNotification('<?php _e('Экспорт выполнен', 'colorful-tables-pro'); ?>'); } else { showNotification(response.data || '<?php _e('Ошибка экспорта', 'colorful-tables-pro'); ?>', 'error'); } }); } function importTable() { var fileInput = document.getElementById('ct-import-file'); if (!fileInput.files.length) { showNotification('<?php _e('Выберите файл', 'colorful-tables-pro'); ?>', 'error'); return; } var reader = new FileReader(); reader.onload = function(e) { try { var data = JSON.parse(e.target.result); $.post(ajaxurl, { action: 'ct_import_table', nonce: '<?php echo wp_create_nonce('ct_ajax_nonce'); ?>', data: data }, function(response) { if (response.success) { location.reload(); } else { showNotification(response.data || '<?php _e('Ошибка импорта', 'colorful-tables-pro'); ?>', 'error'); } }); } catch (err) { showNotification('<?php _e('Неверный формат JSON', 'colorful-tables-pro'); ?>', 'error'); } }; reader.readAsText(fileInput.files[0]); } function searchTables() { var query = $(this).val().toLowerCase(); $('.ct-table-card').each(function() { var name = $(this).data('name').toLowerCase(); $(this).toggle(name.indexOf(query) !== -1); }); } function showNotification(message, type) { type = type || 'success'; var $notification = $('<div class="ct-notification ct-notification-' + type + '">' + message + '</div>'); $('body').append($notification); setTimeout(function() { $notification.fadeOut(300, function() { $(this).remove(); }); }, 3000); } }); </script> <?php } public function ajax_clone_table() { check_ajax_referer('ct_ajax_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error('Недостаточно прав'); } $id = intval($_POST['id']); $tables = get_option($this->storage_key, array()); if (!isset($tables[$id])) { wp_send_json_error('Таблица не найдена'); } $new_id = empty($tables) ? 1 : max(array_keys($tables)) + 1; $tables[$new_id] = $tables[$id]; $tables[$new_id]['name'] = $tables[$id]['name'] . ' (копия)'; update_option($this->storage_key, $tables); wp_send_json_success(); } public function ajax_export_table() { check_ajax_referer('ct_ajax_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error('Недостаточно прав'); } $id = intval($_POST['id']); $tables = get_option($this->storage_key, array()); if (!isset($tables[$id])) { wp_send_json_error('Таблица не найдена'); } wp_send_json_success($tables[$id]); } public function ajax_import_table() { check_ajax_referer('ct_ajax_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error('Недостаточно прав'); } $data = $_POST['data']; if (!is_array($data) || !isset($data['name']) || !isset($data['columns']) || !isset($data['rows'])) { wp_send_json_error('Неверные данные'); } $tables = get_option($this->storage_key, array()); $new_id = empty($tables) ? 1 : max(array_keys($tables)) + 1; $tables[$new_id] = $data; update_option($this->storage_key, $tables); wp_send_json_success(); } private function safe_json_encode($data) { return wp_json_encode($data, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP | JSON_UNESCAPED_UNICODE); } private function get_style_name($style) { $styles = array( 'gradient' => __('Градиентный', 'colorful-tables-pro'), 'modern' => __('Современный', 'colorful-tables-pro'), 'elegant' => __('Элегантный', 'colorful-tables-pro'), 'vibrant' => __('Яркий', 'colorful-tables-pro'), 'minimal' => __('Минимализм', 'colorful-tables-pro') ); return isset($styles[$style]) ? $styles[$style] : $style; } private function sanitize_color($color) { if (empty($color)) return ''; $color = sanitize_hex_color($color); return $color ?: ''; } private function get_frontend_css() { ob_start(); ?> .ct-table-container { margin: 2rem 0; border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.1); background: white; overflow: hidden; } .ct-table-desktop { display: grid; width: 100%; } .ct-table-header { padding: 18px 20px; font-weight: 700; text-align: center; font-size: 16px; word-break: break-word; display: flex; align-items: center; justify-content: center; min-height: 60px; box-sizing: border-box; } .ct-table-cell { padding: 16px 20px; background: #ffffff; min-height: 60px; display: flex; align-items: center; word-break: break-word; line-height: 1.5; box-sizing: border-box; border-bottom: 1px solid #f8f9fa; } .ct-table-cell:nth-child(odd) { background: #fafbfc; } .ct-table-mobile { display: none; } .ct-mobile-row { border-bottom: 1px solid #f0f2f5; padding: 0; } .ct-mobile-row:last-child { border-bottom: none; } .ct-mobile-cell { display: grid; grid-template-columns: 120px 1fr; gap: 15px; padding: 15px 20px; align-items: start; border-bottom: 1px solid #f8f9fa; } .ct-mobile-cell:last-child { border-bottom: none; } .ct-mobile-header { font-weight: 600; color: #2c3e50; font-size: 14px; word-break: break-word; } .ct-mobile-content { word-break: break-word; line-height: 1.5; } .ct-table-container.style-gradient .ct-table-header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; } .ct-table-container.style-modern .ct-table-desktop { border: 1px solid #e0e0e0; } .ct-table-container.style-modern .ct-table-header { background: #2c3e50; color: white; } .ct-table-container.style-elegant .ct-table-desktop { border: 1px solid #d4af37; } .ct-table-container.style-elegant .ct-table-header { background: #d4af37; color: #2c3e50; font-weight: 600; } .ct-table-container.style-vibrant .ct-table-header { background: #e74c3c; color: white; font-weight: 800; } .ct-table-container.style-minimal .ct-table-header { background: none; color: #2c3e50; border-bottom: 2px solid #3498db; font-weight: 600; } @media (max-width: 768px) { .ct-table-desktop { display: none; } .ct-table-mobile { display: block; } .ct-table-container { margin: 1rem 0; border-radius: 8px; } .ct-mobile-cell { grid-template-columns: 100px 1fr; gap: 12px; padding: 12px 15px; } .ct-mobile-header { font-size: 13px; } .ct-mobile-content { font-size: 14px; } } @media (min-width: 769px) { .ct-table-mobile { display: none; } .ct-table-desktop { display: grid; } } <?php return ob_get_clean(); } private function get_admin_css() { ob_start(); ?> .ct-wrapper { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; background: #f8f9fa; min-height: 100vh; } .ct-header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px 0; margin: 0 -20px 20px; } .ct-header-content { max-width: 1400px; margin: 0 auto; padding: 0 20px; } .ct-header h1 { font-size: 2em; margin: 0 0 8px 0; font-weight: 600; color: white; } .ct-subtitle { font-size: 1em; opacity: 0.9; margin: 0; color: white; } .ct-badge { background: rgba(255,255,255,0.2); padding: 4px 10px; border-radius: 16px; font-size: 0.7em; margin-left: 12px; color: white; } .ct-container { max-width: 1400px; margin: 0 auto; display: grid; grid-template-columns: 1fr 350px; gap: 20px; padding: 0 20px; } .ct-main-panel { min-width: 0; } .ct-sidebar { display: grid; gap: 20px; grid-template-rows: auto auto; } .ct-panel { background: white; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); overflow: hidden; } .ct-panel-header { padding: 15px 20px; border-bottom: 1px solid #f0f2f5; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px; } .ct-panel-header h2, .ct-panel-header h3 { margin: 0; font-weight: 600; color: #2c3e50; font-size: 1.1em; } .ct-panel-content { padding: 20px; } .ct-section { background: white; border-radius: 8px; margin-bottom: 20px; border: 1px solid #f0f2f5; } .ct-section-header { padding: 15px 20px; border-bottom: 1px solid #f0f2f5; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px; } .ct-section-header h3 { margin: 0; font-weight: 600; color: #2c3e50; font-size: 1em; } .ct-section-content { padding: 20px; } .ct-form-grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; } .ct-form-group { margin-bottom: 15px; } .ct-label { display: block; margin-bottom: 6px; font-weight: 600; color: #2c3e50; font-size: 0.9em; } .ct-input, .ct-select, .ct-number-input { width: 100%; padding: 10px 12px; border: 1px solid #e9ecef; border-radius: 6px; font-size: 13px; box-sizing: border-box; color: #2c3e50; background: white; } .ct-input:focus, .ct-select:focus { border-color: #3498db; box-shadow: 0 0 0 2px rgba(52,152,219,0.1); outline: none; } .ct-structure-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; } .ct-structure-item h4 { margin: 0 0 12px 0; color: #2c3e50; font-size: 0.95em; } .ct-control-group { display: grid; gap: 12px; } .ct-number-control { display: grid; gap: 8px; } .ct-number-control label { font-weight: 600; color: #2c3e50; margin: 0; font-size: 0.9em; } .ct-number-buttons { display: grid; grid-template-columns: auto 1fr auto; gap: 5px; align-items: center; } .ct-number-btn { background: #f8f9fa; border: 1px solid #e9ecef; width: 35px; height: 35px; border-radius: 6px; display: grid; place-items: center; cursor: pointer; font-size: 16px; font-weight: bold; color: #2c3e50; } .ct-number-btn:hover { background: #3498db; color: white; border-color: #3498db; } .ct-number-input { text-align: center; background: #f8f9fa; height: 35px; } .ct-action-buttons-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; } .ct-button-small { padding: 6px 10px; font-size: 11px; height: auto; } .ct-button-danger { background: #e74c3c; color: white; border: none; } .ct-button-danger:hover { background: #c0392b; color: white; } .ct-structure-info { grid-column: 1 / -1; margin-top: 15px; padding: 12px; background: #e8f6f3; border-radius: 6px; border: 1px solid #2ecc71; color: #2c3e50; } .ct-structure-stat { text-align: center; font-size: 13px; color: #27ae60; } .ct-color-presets-grid { display: flex; gap: 8px; align-items: center; font-size: 12px; color: #6c757d; flex-wrap: wrap; } .ct-color-swatch { width: 22px; height: 22px; border: 2px solid white; border-radius: 4px; cursor: pointer; box-shadow: 0 1px 2px rgba(0,0,0,0.1); transition: all 0.2s ease; } .ct-color-swatch:hover { transform: scale(1.15); } .ct-headers-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 15px; } .ct-header-card { background: #f8f9fa; padding: 15px; border-radius: 8px; border: 1px solid transparent; color: #2c3e50; } .ct-header-card:hover { border-color: #3498db; } .ct-card-header-grid { display: grid; grid-template-columns: 1fr auto; gap: 10px; align-items: center; margin-bottom: 12px; } .ct-card-header-grid label { font-size: 0.9em; font-weight: 600; color: #495057; margin: 0; } .ct-color-preview { width: 22px; height: 22px; border-radius: 4px; border: 2px solid white; box-shadow: 0 1px 3px rgba(0,0,0,0.1); } .ct-color-controls-grid { display: grid; gap: 12px; margin-top: 12px; } .ct-color-group-grid { display: grid; grid-template-columns: auto 1fr; gap: 8px; align-items: center; } .ct-color-group-grid label { font-size: 11px; font-weight: 600; color: #6c757d; margin: 0; min-width: 40px; } .ct-color-input-grid { display: grid; grid-template-columns: auto 1fr; gap: 6px; align-items: center; } .ct-color-input { width: 40px !important; height: 32px; border: 1px solid #e9ecef; border-radius: 4px; cursor: pointer; padding: 0; } .ct-color-hex { padding: 6px 8px; border: 1px solid #e9ecef; border-radius: 4px; font-family: monospace; font-size: 11px; height: 32px; box-sizing: border-box; color: #2c3e50; background: white; } .ct-rows-container { max-height: 500px; overflow-y: auto; background: #fafbfc; padding: 15px; border-radius: 8px; border: 1px dashed #e9ecef; } .ct-row-card { background: white; padding: 15px; border-radius: 8px; margin-bottom: 15px; border: 1px solid #f0f2f5; color: #2c3e50; } .ct-row-card:hover { border-color: #3498db; } .ct-row-header-grid { display: grid; grid-template-columns: 1fr auto; gap: 10px; align-items: center; margin-bottom: 12px; } .ct-row-header-grid span { font-weight: 600; color: #2c3e50; font-size: 0.9em; } .ct-clear-row { background: none; border: 1px solid #e74c3c; color: #e74c3c; padding: 3px 6px; border-radius: 3px; font-size: 10px; cursor: pointer; } .ct-clear-row:hover { background: #e74c3c; color: white; } .ct-cells-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; } .ct-cell-card { background: #f8f9fa; padding: 12px; border-radius: 6px; border: 1px solid transparent; color: #2c3e50; } .ct-cell-card:hover { border-color: #3498db; } .ct-cell-header-grid { display: grid; grid-template-columns: 1fr auto; gap: 8px; align-items: center; margin-bottom: 8px; } .ct-cell-header-grid label { font-size: 10px; font-weight: 600; color: #6c757d; margin: 0; } .ct-cell-card textarea { width: 100%; min-height: 50px; padding: 8px; border: 1px solid #e9ecef; border-radius: 4px; resize: vertical; font-size: 12px; box-sizing: border-box; color: #2c3e50; background: white; } .ct-actions { margin-top: 20px; padding-top: 20px; border-top: 1px solid #f0f2f5; } .ct-main-actions-grid { display: grid; grid-template-columns: auto auto auto; gap: 8px; justify-content: end; } .ct-button-primary { background: #3498db; border: none; padding: 10px 20px; border-radius: 6px; font-weight: 600; font-size: 13px; color: white; } .ct-button-primary:hover { background: #2980b9; transform: translateY(-1px); color: white; } .ct-button-secondary { background: #f8f9fa; border: 1px solid #e9ecef; color: #2c3e50; padding: 8px 16px; border-radius: 6px; font-weight: 500; font-size: 13px; } .ct-button-secondary:hover { background: #3498db; color: white; border-color: #3498db; } .ct-icon { margin-right: 4px; } .ct-tables-grid { display: grid; gap: 12px; } .ct-table-card { background: #f8f9fa; padding: 15px; border-radius: 8px; border: 1px solid transparent; color: #2c3e50; } .ct-table-card:hover { border-color: #3498db; } .ct-table-header-grid { display: grid; grid-template-columns: 1fr auto; gap: 8px; align-items: center; margin-bottom: 8px; } .ct-table-header-grid h4 { margin: 0; color: #2c3e50; font-size: 0.95em; } .ct-table-badge { background: #3498db; color: white; padding: 2px 8px; border-radius: 12px; font-size: 10px; font-weight: 600; } .ct-table-meta-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 10px; } .ct-meta-item { font-size: 11px; color: #6c757d; } .ct-table-shortcode-grid { display: grid; grid-template-columns: 1fr auto; gap: 8px; align-items: center; margin-bottom: 10px; } .ct-table-shortcode-grid code { background: white; padding: 6px 8px; border-radius: 4px; font-size: 11px; border: 1px solid #e9ecef; overflow: hidden; text-overflow: ellipsis; color: #2c3e50; } .ct-copy-btn { background: #f8f9fa; border: 1px solid #e9ecef; padding: 6px; border-radius: 4px; cursor: pointer; height: 30px; width: 30px; display: grid; place-items: center; color: #2c3e50; } .ct-copy-btn:hover { background: #3498db; color: white; } .ct-table-actions-grid { display: grid; grid-template-columns: 1fr auto auto auto; gap: 6px; align-items: center; } .ct-delete-form { margin: 0; display: inline; } .ct-search-input { max-width: 200px; padding: 5px 10px; font-size: 12px; } .ct-pagination { margin-top: 15px; text-align: center; } .ct-pagination .page-numbers { display: inline-block; padding: 5px 10px; margin: 0 2px; border: 1px solid #e9ecef; border-radius: 4px; color: #2c3e50; text-decoration: none; } .ct-pagination .page-numbers.current { background: #3498db; color: white; border-color: #3498db; } .ct-import-area { margin-top: 15px; padding-top: 15px; border-top: 1px solid #f0f2f5; } .ct-import-area h4 { margin: 0 0 10px 0; font-size: 13px; color: #2c3e50; } .ct-import-area input[type="file"] { margin-bottom: 8px; } .ct-empty-state { text-align: center; padding: 30px 15px; color: #6c757d; } .ct-empty-icon { font-size: 2.5em; margin-bottom: 10px; } .ct-stats-panel .ct-panel-content { padding: 15px; } .ct-stats-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; } .ct-stat-item { text-align: center; padding: 12px; background: #f8f9fa; border-radius: 6px; color: #2c3e50; } .ct-stat-number { font-size: 1.5em; font-weight: bold; color: #3498db; margin-bottom: 4px; } .ct-stat-label { font-size: 0.75em; color: #6c757d; } .ct-modal { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 9999; display: none; place-items: center; } .ct-modal-content { background: white; border-radius: 8px; width: 90%; max-width: 700px; max-height: 70vh; overflow: hidden; box-shadow: 0 10px 30px rgba(0,0,0,0.3); } .ct-modal-header-grid { padding: 15px 20px; border-bottom: 1px solid #f0f2f5; display: grid; grid-template-columns: 1fr auto; gap: 15px; align-items: center; } .ct-modal-header-grid h3 { margin: 0; color: #2c3e50; font-size: 1em; } .ct-modal-close { background: none; border: none; font-size: 24px; cursor: pointer; color: #6c757d; padding: 0; width: 30px; height: 30px; display: grid; place-items: center; } .ct-modal-body { padding: 20px; max-height: 50vh; overflow-y: auto; } .ct-notification { position: fixed; top: 15px; right: 15px; background: #2ecc71; color: white; padding: 12px 20px; border-radius: 8px; box-shadow: 0 3px 10px rgba(0,0,0,0.2); z-index: 10000; font-weight: 500; font-size: 14px; } .ct-notification-error { background: #e74c3c; } @media (max-width: 1200px) { .ct-container { grid-template-columns: 1fr; } .ct-sidebar { grid-template-columns: 1fr 1fr; grid-template-rows: auto; } .ct-structure-grid { grid-template-columns: 1fr; } } @media (max-width: 768px) { .ct-header h1 { font-size: 1.6em; } .ct-form-grid-2 { grid-template-columns: 1fr; } .ct-headers-grid { grid-template-columns: 1fr; } .ct-cells-grid { grid-template-columns: 1fr; } .ct-main-actions-grid { grid-template-columns: 1fr; justify-content: stretch; } .ct-sidebar { grid-template-columns: 1fr; } .ct-table-actions-grid { grid-template-columns: 1fr 1fr; } } <?php return ob_get_clean(); } private function save_table() { if (!current_user_can('manage_options')) { return; } $table_id = isset($_POST['edit_table_id']) ? intval($_POST['edit_table_id']) : null; $table_data = array( 'name' => sanitize_text_field($_POST['table_name']), 'columns' => intval($_POST['columns']), 'style' => sanitize_text_field($_POST['style']), 'headers' => array(), 'rows' => array() ); if (isset($_POST['headers']) && is_array($_POST['headers'])) { foreach ($_POST['headers'] as $header) { $table_data['headers'][] = array( 'content' => sanitize_text_field($header['content']), 'bg_color' => $this->sanitize_color($header['bg_color']), 'text_color' => $this->sanitize_color($header['text_color']) ); } } if (isset($_POST['rows']) && is_array($_POST['rows'])) { foreach ($_POST['rows'] as $row) { $row_data = array('cells' => array()); if (isset($row['cells']) && is_array($row['cells'])) { foreach ($row['cells'] as $cell) { $row_data['cells'][] = array( 'content' => wp_kses_post($cell['content']), 'bg_color' => $this->sanitize_color($cell['bg_color']), 'text_color' => $this->sanitize_color($cell['text_color']) ); } } $table_data['rows'][] = $row_data; } } $tables = get_option($this->storage_key, array()); if ($table_id && isset($tables[$table_id])) { $tables[$table_id] = $table_data; } else { $new_id = empty($tables) ? 1 : max(array_keys($tables)) + 1; $tables[$new_id] = $table_data; } update_option($this->storage_key, $tables); $this->clear_table_cache($table_id ?: $new_id); } private function delete_table() { if (!current_user_can('manage_options')) { return; } $table_id = intval($_POST['delete_table']); $tables = get_option($this->storage_key, array()); if (isset($tables[$table_id])) { unset($tables[$table_id]); update_option($this->storage_key, $tables); $this->clear_table_cache($table_id); } } private function clear_table_cache($table_id) { delete_transient('colorful_table_' . $table_id); } public function shortcode($atts) { $atts = shortcode_atts(array('id' => '0'), $atts); $table_id = intval($atts['id']); if ($table_id <= 0) { return '<p>' . __('Неверный ID таблицы', 'colorful-tables-pro') . '</p>'; } $cache_key = 'colorful_table_' . $table_id; $cached = get_transient($cache_key); if ($cached !== false) { return $cached; } $tables = get_option($this->storage_key, array()); if (!isset($tables[$table_id])) { return '<p>' . __('Таблица не найдена', 'colorful-tables-pro') . '</p>'; } $output = $this->render_table($tables[$table_id]); set_transient($cache_key, $output, HOUR_IN_SECONDS); return $output; } private function render_table($table) { $columns = $table['columns']; $headers = $table['headers']; $rows = $table['rows']; $style = $table['style']; $output = '<div class="ct-table-container style-' . esc_attr($style) . '">'; $output .= '<div class="ct-table-desktop" style="grid-template-columns: repeat(' . esc_attr($columns) . ', 1fr)">'; foreach ($headers as $header) { $header_style = ''; if (!empty($header['bg_color'])) $header_style .= 'background-color: ' . esc_attr($header['bg_color']) . ';'; if (!empty($header['text_color'])) $header_style .= 'color: ' . esc_attr($header['text_color']) . ';'; $output .= '<div class="ct-table-header" style="' . $header_style . '">' . esc_html($header['content']) . '</div>'; } foreach ($rows as $row) { foreach ($row['cells'] as $cell) { $cell_style = ''; if (!empty($cell['bg_color'])) $cell_style .= 'background-color: ' . esc_attr($cell['bg_color']) . ';'; if (!empty($cell['text_color'])) $cell_style .= 'color: ' . esc_attr($cell['text_color']) . ';'; $output .= '<div class="ct-table-cell" style="' . $cell_style . '">' . wp_kses_post($cell['content']) . '</div>'; } } $output .= '</div>'; $output .= '<div class="ct-table-mobile">'; foreach ($rows as $row) { $output .= '<div class="ct-mobile-row">'; foreach ($row['cells'] as $cell_index => $cell) { if (isset($headers[$cell_index])) { $cell_style = ''; if (!empty($cell['bg_color'])) $cell_style .= 'background-color: ' . esc_attr($cell['bg_color']) . ';'; if (!empty($cell['text_color'])) $cell_style .= 'color: ' . esc_attr($cell['text_color']) . ';'; $output .= '<div class="ct-mobile-cell" style="' . $cell_style . '">'; $output .= '<div class="ct-mobile-header">' . esc_html($headers[$cell_index]['content']) . '</div>'; $output .= '<div class="ct-mobile-content">' . wp_kses_post($cell['content']) . '</div>'; $output .= '</div>'; } } $output .= '</div>'; } $output .= '</div></div>'; return $output; } } new ColorfulTablesPro(); ?>

ИТОГ: ЧТО Я ПОЛУЧИЛ?
Теперь у меня есть:

✅ Идеальный конструктор таблиц под мои задачи

✅ 90+ баллов PageSpeed — сайт не тормозит

✅ Полный контроль над дизайном — цвета, стили, адаптив

✅ Экспорт и импорт — таблицы можно переносить между сайтами

✅ Клонирование — копирую таблицы за секунду

✅ Поиск и пагинация — удобно работать с десятками таблиц

И всё это без единой строки кода, написанной мной. DeepSeek сделал всю работу. Я просто объяснял, что хочу.

А ВЫ ПОЛЬЗУЕТЕСЬ КАСТОМНЫМИ ПЛАГИНАМИ?
Поделитесь своим опытом в комментариях! Как вы решаете задачи, для которых нет готовых решений? Может, тоже пробовали создавать свои плагины с AI?

И если хотите скачать этот плагин — вот ссылка:
Colorful-Tables-Pro

P.S. Эту статью тоже помогал писать DeepSeek. Я просто поделился мыслями, а он оформил их в связный текст. Работа в тандеме — это действительно круто! 🤝

Вот ещё второй архив плагина если первый не работает:
скачать

Оставить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

🧮 Проверка безопасности
20 - 15 = ?