Знакомо чувство, когда перепробовал кучу плагинов для создания таблиц в WordPress, а всё не то? То функционала не хватает, то интерфейс запутанный, то таблицы криво смотрятся на мобильных. Я через это прошёл.
Мне надоело постоянно искать компромиссы между красотой и функциональностью. Хотелось просто брать и создавать красивые, адаптивные таблицы с цветовым оформлением, без танцев с бубнами вокруг коротких кодов и настроек.
Решил — хватит это терпеть! Буду делать свой плагин, который будет делать именно то, что мне нужно.
Но я же не профессиональный разработчик. Знаю WordPress на уровне пользователя, с кодом на «вы». Поэтому обратился за помощью к DeepSeek — как к техническому напарнику.
И знаете, что удивительно? Процесс создания оказался не таким страшным, как я думал. Я описывал, что хочу видеть в интерфейсе, как должна работать цветовая палитра, какая нужна адаптивность для мобильных устройств. DeepSeek помогал с технической реализацией, предлагал решения, исправлял ошибки.
Мы работали в тандеме: я — как «продукт-менеджер» с пониманием потребностей, DeepSeek — как разработчик, который превращал мои идеи в рабочий код.
Результат превзошёл все мои ожидания! Получился плагин, в котором:
— Интуитивно понятный конструктор — никаких сложных настроек
— Красивые цветовые схемы на выбор
— Полная адаптивность — таблицы идеально смотрятся на любых устройствах
— Управление колонками и строками в пару кликов
— Встроенный предпросмотр перед публикацией
Самое приятное — когда проверил скорость сайта с плагином, оказалось что он не только удобный, но и не тормозит сайт! PageSpeed показывает 90+ баллов даже с тяжёлыми изображениями и рекламой.
Теперь я создаю таблицы за минуты, а не часы. И главное — получаю именно тот результат, который хотел.
Если вы тоже устали бороться с неудобными плагинами — возможно, стоит попробовать создать свой? С современными AI-инструментами это гораздо проще, чем кажется!
Это сам плагин:
<?php
/**
* Plugin Name: Colorful Tables Pro
* Description: Продвинутый конструктор таблиц с управлением колонками и строками
* Version: 1.0.5
* Author: Your Name
* Text Domain: colorful-tables-pro
*/
if (!defined('ABSPATH')) {
exit;
}
class ColorfulTablesPro {
private $editing_table_id = null;
private $storage_key = 'colorful_tables_pro_data';
private $max_columns = 10;
private $max_rows = 30;
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'));
register_activation_hook(__FILE__, array($this, 'activate'));
register_deactivation_hook(__FILE__, array($this, 'deactivate'));
}
public function activate() {
if (!current_user_can('activate_plugins')) {
return;
}
// Initialize default options
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;
if (isset($_GET['edit_table'])) {
$this->editing_table_id = intval($_GET['edit_table']);
if (isset($tables[$this->editing_table_id])) {
$edit_table_data = $tables[$this->editing_table_id];
}
}
$edit_data_js = $edit_table_data ? $this->safe_json_encode($edit_table_data) : 'null';
?>
<div class="wrap ct-wrapper <?php echo $this->editing_table_id ? 'ct-editing' : ''; ?>">
<!-- Header -->
<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 ($this->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">
<!-- Main Constructor -->
<div class="ct-main-panel">
<div class="ct-panel ct-builder-panel">
<div class="ct-panel-header">
<h2>
<?php if ($this->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 ($this->editing_table_id): ?>
<input type="hidden" name="edit_table_id" value="<?php echo esc_attr($this->editing_table_id); ?>">
<?php endif; ?>
<!-- Basic Settings -->
<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">
<span class="ct-icon"></span> <?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">
<span class="ct-icon"></span> <?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>
<!-- Table Structure -->
<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><span class="ct-icon"></span> <?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">
<span class="ct-icon">➕</span> <?php _e('Добавить колонку', 'colorful-tables-pro'); ?>
</button>
<button type="button" class="button ct-button-small ct-button-danger" id="ct-remove-column">
<span class="ct-icon">➖</span> <?php _e('Удалить колонку', 'colorful-tables-pro'); ?>
</button>
</div>
</div>
</div>
<div class="ct-structure-item">
<h4><span class="ct-icon"></span> <?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">
<span class="ct-icon">➕</span> <?php _e('Добавить строку', 'colorful-tables-pro'); ?>
</button>
<button type="button" class="button ct-button-small ct-button-danger" id="ct-remove-row">
<span class="ct-icon">➖</span> <?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>
<!-- Headers -->
<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" title="Синий" style="background: #3498db;"></button>
<button type="button" class="ct-color-swatch" data-bg="#2ecc71" data-text="#ffffff" title="Зеленый" style="background: #2ecc71;"></button>
<button type="button" class="ct-color-swatch" data-bg="#e74c3c" data-text="#ffffff" title="Красный" style="background: #e74c3c;"></button>
<button type="button" class="ct-color-swatch" data-bg="#f39c12" data-text="#ffffff" title="Оранжевый" style="background: #f39c12;"></button>
<button type="button" class="ct-color-swatch" data-bg="#9b59b6" data-text="#ffffff" title="Фиолетовый" style="background: #9b59b6;"></button>
</div>
</div>
<div class="ct-section-content">
<div id="ct-headers-container" class="ct-headers-grid">
<!-- Headers generated by JS -->
</div>
</div>
</div>
<!-- Table Data -->
<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" title="Светлый" style="background: #ecf0f1;"></button>
<button type="button" class="ct-color-swatch" data-bg="#fff9e6" data-text="#e67e22" title="Кремовый" style="background: #fff9e6;"></button>
<button type="button" class="ct-color-swatch" data-bg="#e8f6f3" data-text="#27ae60" title="Мятный" style="background: #e8f6f3;"></button>
</div>
<button type="button" class="button ct-button-small" id="ct-clear-all">
<span class="ct-icon"></span> <?php _e('Очистить все', 'colorful-tables-pro'); ?>
</button>
</div>
</div>
<div class="ct-section-content">
<div id="ct-rows-container" class="ct-rows-container">
<!-- Rows generated by JS -->
</div>
</div>
</div>
<!-- Actions -->
<div class="ct-actions">
<div class="ct-main-actions-grid">
<button type="button" class="button ct-button-secondary" id="ct-preview">
<span class="ct-icon">️</span> <?php _e('Предпросмотр', 'colorful-tables-pro'); ?>
</button>
<?php if ($this->editing_table_id): ?>
<a href="<?php echo admin_url('admin.php?page=colorful-tables-pro'); ?>" class="button">
<span class="ct-icon">↩️</span> <?php _e('Отмена', 'colorful-tables-pro'); ?>
</a>
<?php endif; ?>
<button type="submit" name="save_table" class="button button-primary ct-button-primary">
<span class="ct-icon"></span> <?php echo $this->editing_table_id ? __('Обновить таблицу', 'colorful-tables-pro') : __('Сохранить таблицу', 'colorful-tables-pro'); ?>
</button>
</div>
</div>
</form>
</div>
</div>
<!-- Sidebar -->
<div class="ct-sidebar">
<!-- Tables List -->
<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>
<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">
<?php foreach ($tables as $id => $table): ?>
<div class="ct-table-card">
<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">
<span class="ct-icon"></span> <?php echo $table['columns']; ?>×<?php echo count($table['rows']); ?>
</span>
<span class="ct-meta-item">
<span class="ct-icon"></span> <?php echo $this->get_style_name($table['style']); ?>
</span>
</div>
<div class="ct-table-shortcode-grid">
<code><p>Неверный ID таблицы</p></code>
<button type="button" class="ct-copy-btn" data-shortcode='[colorful_table id="<?php echo $id; ?>"]'>
<span class="ct-icon"></span>
</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">
<span class="ct-icon">✏️</span> <?php _e('Редактировать', 'colorful-tables-pro'); ?>
</a>
<form method="post" class="ct-delete-form">
<?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'); ?>')">
<span class="ct-icon">️</span>
</button>
</form>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</div>
<!-- Statistics -->
<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>
<!-- Preview Modal -->
<div id="ct-preview-modal" class="ct-modal" style="display: none;">
<div class="ct-modal-content">
<div class="ct-modal-header-grid">
<h3><span class="ct-icon">️</span> <?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; ?>;
// Initialize table builder
initTableBuilder();
// Event handlers
$(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);
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}">`;
html += `<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>`;
html += `<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'},
{bg: '#1abc9c', text: '#ffffff'},
{bg: '#34495e', text: '#ffffff'},
{bg: '#e67e22', text: '#ffffff'},
{bg: '#16a085', text: '#ffffff'},
{bg: '#8e44ad', 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);
if (target === 'columns') {
generateHeaders();
generateRows();
} else {
generateRows();
}
updateStructureInfo();
}
} else if (action === 'decrease') {
if (current > 1) {
$input.val(current - 1);
if (target === 'columns') {
generateHeaders();
generateRows();
} else {
generateRows();
}
updateStructureInfo();
}
}
}
function addColumn() {
var $input = $('#ct-columns');
var current = parseInt($input.val());
if (current < maxColumns) {
$input.val(current + 1);
generateHeaders();
generateRows();
updateStructureInfo();
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);
generateHeaders();
generateRows();
updateStructureInfo();
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);
generateRows();
updateStructureInfo();
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);
generateRows();
updateStructureInfo();
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'); ?> ' + (row + 1) + ' <?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;
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);
}
});
// Update color previews
$('.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();
// Collect data for preview
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 });
});
// Generate preview HTML
var previewHtml = '<div class="ct-table-container style-' + style + '">';
// Desktop version
previewHtml += '<div class="ct-table-desktop" style="grid-template-columns: repeat(' + columns + ', 1fr)">';
// Headers for desktop
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 + '">';
previewHtml += header.content || '<?php _e('Заголовок', 'colorful-tables-pro'); ?>';
previewHtml += '</div>';
});
// Cells for desktop
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 + '">';
previewHtml += cell.content || '<?php _e('Данные', 'colorful-tables-pro'); ?>';
previewHtml += '</div>';
});
});
previewHtml += '</div>'; // .ct-table-desktop
// Mobile version
previewHtml += '<div class="ct-table-mobile">';
tableRows.forEach(function(row, rowIndex) {
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>'; // .ct-mobile-row
});
previewHtml += '</div>'; // .ct-table-mobile
previewHtml += '</div>'; // .ct-table-container
$('#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('<span class="ct-icon">✅</span>');
setTimeout(function() {
$btn.html(originalHtml);
}, 2000);
showNotification('<?php _e('Шорткод скопирован в буфер обмена', 'colorful-tables-pro'); ?>');
}.bind(this));
}
function showNotification(message, 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
}
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;
}
/* Desktop version */
.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;
transition: all 0.3s ease;
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;
transition: all 0.3s ease;
box-sizing: border-box;
border-bottom: 1px solid #f8f9fa;
}
.ct-table-cell:nth-child(odd) {
background: #fafbfc;
}
/* Mobile version */
.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;
}
/* Styles for different themes */
.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;
}
/* Responsive media queries */
@media (max-width: 768px) {
.ct-table-desktop {
display: none;
}
.ct-table-mobile {
display: block;
}
.ct-table-container {
margin: 1rem 0;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.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-logo {
font-size: 1.1em;
margin-right: 8px;
}
.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;
}
.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;
}
.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;
transition: all 0.2s ease;
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;
transition: all 0.2s ease;
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-presets-grid span {
white-space: nowrap;
}
.ct-color-swatch {
width: 18px;
height: 18px;
border: 2px solid white;
border-radius: 3px;
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.1);
}
.ct-table-actions-grid {
display: grid;
grid-template-columns: 1fr auto;
gap: 10px;
align-items: center;
}
.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;
transition: all 0.2s ease;
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: 20px;
height: 20px;
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;
transition: all 0.2s ease;
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;
transition: all 0.2s ease;
}
.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;
transition: all 0.2s ease;
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;
transition: all 0.2s ease;
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;
transition: all 0.2s ease;
}
.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;
transition: all 0.2s ease;
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 6px;
border-radius: 8px;
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;
transition: all 0.2s ease;
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;
gap: 6px;
}
.ct-delete-form {
margin: 0;
}
.ct-empty-state {
text-align: center;
padding: 30px 15px;
color: #6c757d;
}
.ct-empty-icon {
font-size: 2.5em;
margin-bottom: 10px;
}
.ct-empty-hint {
font-size: 0.8em;
opacity: 0.7;
}
.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: 20px;
cursor: pointer;
color: #6c757d;
padding: 0;
width: 25px;
height: 25px;
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: 10px 15px;
border-radius: 6px;
box-shadow: 0 3px 10px rgba(0,0,0,0.2);
z-index: 10000;
font-weight: 500;
font-size: 13px;
}
.ct-notification-error {
background: #e74c3c;
}
/* Fix text colors in all elements */
.ct-header-card input,
.ct-cell-card textarea,
.ct-color-hex,
.ct-row-card span,
.ct-table-card h4 {
color: #2c3e50 !important;
}
@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-table-actions-grid {
grid-template-columns: 1fr;
}
.ct-sidebar {
grid-template-columns: 1fr;
}
.ct-color-presets-grid {
grid-template-columns: 1fr;
}
}
<?php
return ob_get_clean();
}
private function save_table() {
if (!current_user_can('manage_options')) {
$this->log_error('Unauthorized table save attempt');
return;
}
$table_id = isset($_POST['edit_table_id']) ? intval($_POST['edit_table_id']) : null;
// Validate and sanitize data
$table_data = array(
'name' => sanitize_text_field($_POST['table_name']),
'columns' => intval($_POST['columns']),
'style' => sanitize_text_field($_POST['style']),
'headers' => array(),
'rows' => array()
);
// Sanitize headers
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'])
);
}
}
// Sanitize rows and cells
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);
// Clear cache
$this->clear_table_cache($table_id ?: $new_id);
}
private function delete_table() {
if (!current_user_can('manage_options')) {
$this->log_error('Unauthorized table delete attempt');
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);
}
private function log_error($message) {
if (WP_DEBUG === true) {
error_log('Colorful Tables Pro: ' . $message);
}
}
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>';
}
// Check cache
$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]);
// Cache for 1 hour
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) . '">';
// Desktop version
$output .= '<div class="ct-table-desktop" style="grid-template-columns: repeat(' . esc_attr($columns) . ', 1fr)">';
// Headers for desktop
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 . '">';
$output .= esc_html($header['content']);
$output .= '</div>';
}
// Cells for desktop
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 . '">';
$output .= wp_kses_post($cell['content']);
$output .= '</div>';
}
}
$output .= '</div>'; // .ct-table-desktop
// Mobile version
$output .= '<div class="ct-table-mobile">';
foreach ($rows as $row_index => $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>'; // .ct-mobile-row
}
$output .= '</div>'; // .ct-table-mobile
$output .= '</div>'; // .ct-table-container
return $output;
}
}
new ColorfulTablesPro();
А этот плагин я выложу в открытый доступ — может, кому-то ещё пригодится решение, выстраданное реальными потребностями.
P.S. Кстати, эту статью тоже помогал писать DeepSeek — я просто поделился мыслями, а он оформил их в связный текст. Работа в тандеме — это круто!
Скачать архив с плагином: скачать
Поделитесь своим опытом в комментариях!
Добавить комментарий