Как неудобство стандартных плагинов привело меня к созданию идеального конструктора таблиц для WordPress
КАК Я ПЕРЕСТАЛ МУЧАТЬСЯ С ТАБЛИЦАМИ В WORDPRESS И СДЕЛАЛ ИДЕАЛЬНЫЙ ПЛАГИН ЗА ВЕЧЕР
Знакомо? Сидишь, перебираешь десятки плагинов для таблиц — один слишком тяжёлый, второй не адаптивный, третий вообще платный, а в бесплатной версии даже цвет ячейки не поменять. Я через это прошёл. И знаете что? Я сделал свой плагин. И он оказался лучше всех, что я пробовал.
ПОЧЕМУ ВСЕ ПЛАГИНЫ — ЭТО КОМПРОМИСС?
Каждый плагин для таблиц в WordPress — это компромисс. Где-то интерфейс как из 2010 года, где-то таблицы ломаются на мобилках, а где-то производительность падает до 20 баллов в PageSpeed.
Мне нужно было простое:
Красивые цветные таблицы
Адаптив под мобильные
Удобный конструктор без коротких кодов
И чтобы сайт не тормозил
Я искал месяц. Не нашёл. Решил — напишу сам.
Я НЕ РАЗРАБОТЧИК, НО У МЕНЯ БЫЛ DeepSeek
Я знаю WordPress как пользователь. С кодом на «вы». PHP для меня — тёмный лес. Поэтому я пошёл к DeepSeek и сказал:
«Вот что я хочу. Помоги сделать работающий плагин».
И DeepSeek стал моим техническим напарником. Мы работали так:
Я описывал интерфейс — кнопки, цвета, поля.
DeepSeek писал код и объяснял, что делает каждая строка.
Я тестировал — находил баги, DeepSeek исправлял.
Я просил улучшения — DeepSeek добавлял фичи.
Весь процесс занял один вечер. А теперь у меня есть плагин, который делает ровно то, что мне нужно.
ЧТО УМЕЕТ МОЙ ПЛАГИН СЕЙЧАС?
Вот список возможностей, которых мне не хватало во всех существующих решениях:
Функция Описание
🎨 5 стилей оформления Градиентный, современный, элегантный, яркий, минимализм
📱 Адаптивность На мобильных таблица превращается в карточки
🖌️ Цвета для каждой ячейки Фон и текст настраиваются индивидуально
➕➖ Добавление/удаление Колонки и строки — в один клик
👁️ Предпросмотр Перед сохранением видите, как будет выглядеть таблица
📋 Копирование шорткода Один клик — и шорткод у вас в буфере
📤 Экспорт/импорт Таблицы можно сохранять в JSON и переносить на другие сайты
🌀 Клонирование Создаёте копию таблицы за секунду
🔍 Поиск по таблицам Быстро находите нужную среди десятков
📄 Пагинация Список таблиц разбит на страницы, если их много
⚡ Оптимизация Кэширование, легковесный CSS, 90+ баллов PageSpeed
КАК ЭТО ВЫГЛЯДИТ В ИНТЕРФЕЙСЕ
Всё максимально просто:
Заходите в админку → новый пункт меню «Colorful Tables Pro»
Вводите название таблицы, выбираете стиль
Указываете количество колонок и строк (кнопками +/−)
Заполняете заголовки и ячейки — можно менять цвета
Нажимаете «Предпросмотр» — видите, как будет на сайте
Сохраняете — получаете шорткод
Всё. Никаких танцев с бубном.
ВОТ КОД ПЛАГИНА (исправленная и улучшенная версия)
Я переписал плагин, добавил новые фичи и исправил все баги. Теперь он ещё лучше:
<?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>✅ ' . __('Таблица сохранена успешно!', '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>🗑️ ' . __('Таблица удалена!', '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">🎨</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">✏️</span> <?php _e('Редактирование таблицы', 'colorful-tables-pro'); ?>
<?php else: ?>
<span class="ct-icon">➕</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">⚙️</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">📐</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">➕ <?php _e('Добавить', 'colorful-tables-pro'); ?></button>
<button type="button" class="button ct-button-small ct-button-danger" id="ct-remove-column">➖ <?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">➕ <?php _e('Добавить', 'colorful-tables-pro'); ?></button>
<button type="button" class="button ct-button-small ct-button-danger" id="ct-remove-row">➖ <?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">📋</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">📊</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">🧹 <?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">👁️ <?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">↩️ <?php _e('Отмена', 'colorful-tables-pro'); ?></a>
<?php endif; ?>
<button type="submit" name="save_table" class="button button-primary ct-button-primary">
💾 <?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">📚</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">📭</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">📏 <?php echo $table['columns']; ?>×<?php echo count($table['rows']); ?></span>
<span class="ct-meta-item">🎨 <?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 таблицы
'>📋</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">✏️ <?php _e('Редактировать', 'colorful-tables-pro'); ?></a>
<button type="button" class="button ct-button-small ct-clone-btn" data-id="<?php echo $id; ?>">🔄 <?php _e('Клонировать', 'colorful-tables-pro'); ?></button>
<button type="button" class="button ct-button-small ct-export-btn" data-id="<?php echo $id; ?>">📤</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'); ?>')">🗑️</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">📥 <?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">📊</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>👁️ <?php _e('Предпросмотр таблицы', 'colorful-tables-pro'); ?></h3>
<button type="button" class="ct-modal-close">×</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}">🧹 <?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('✅');
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. Я просто поделился мыслями, а он оформил их в связный текст. Работа в тандеме — это действительно круто! 🤝
Вот ещё второй архив плагина если первый не работает:
скачать