68f4560bd5
Adds a new "Цифровой бейдж" tab for preparing 240×240 PNG circles for round digital displays. Photos come from device upload or the existing Flickr selection. Each photo can be drag-cropped and zoomed (mouse, wheel, or pinch on touch), have a yellow price tag (arc band or rectangular plate, with auto thousands separator and ₽), and a curved nickname signature — which auto-moves to the top of the circle when a price tag occupies the bottom. Saves are routed through the Web Share API on phones so the badge lands in the iOS/Android Photos app directly; history of saved badges is kept under data/badges/. Default nickname is stored in Settings. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
902 lines
52 KiB
PHP
902 lines
52 KiB
PHP
<?php
|
||
/**
|
||
* VH Posting System - Главная страница
|
||
* Управление фотографиями Flickr и публикация в соцсети
|
||
*/
|
||
|
||
session_start();
|
||
|
||
// Load configuration
|
||
$configFile = __DIR__ . '/config.php';
|
||
if (!file_exists($configFile)) {
|
||
die('Файл конфигурации не найден. Скопируйте config.example.php в config.php и настройте его.');
|
||
}
|
||
$config = require $configFile;
|
||
|
||
// Autoload classes
|
||
spl_autoload_register(function ($class) {
|
||
$file = __DIR__ . '/classes/' . $class . '.php';
|
||
if (file_exists($file)) {
|
||
require_once $file;
|
||
}
|
||
});
|
||
|
||
// Initialize Auth
|
||
$auth = new Auth();
|
||
|
||
// Handle setup if no users exist
|
||
if (!$auth->hasUsers()) {
|
||
header('Location: setup.php');
|
||
exit;
|
||
}
|
||
|
||
// Check authentication
|
||
if (!$auth->isAuthenticated()) {
|
||
header('Location: login.php');
|
||
exit;
|
||
}
|
||
|
||
// Get current user
|
||
$currentUser = $auth->getCurrentUser();
|
||
|
||
// Handle logout
|
||
if (isset($_GET['logout'])) {
|
||
$auth->logout();
|
||
header('Location: login.php');
|
||
exit;
|
||
}
|
||
|
||
?>
|
||
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>VH Posting System</title>
|
||
<link rel="icon" type="image/png" href="image.png">
|
||
<link rel="stylesheet" href="css/style.css?v=<?= filemtime(__DIR__ . '/css/style.css') ?>">
|
||
<script>
|
||
// Apply saved theme immediately to prevent flash
|
||
(function() {
|
||
const theme = localStorage.getItem('theme') || 'light';
|
||
if (theme === 'dark') {
|
||
document.documentElement.setAttribute('data-theme', 'dark');
|
||
}
|
||
})();
|
||
</script>
|
||
</head>
|
||
<body>
|
||
<div class="app-container">
|
||
<!-- Header -->
|
||
<header class="app-header">
|
||
<h1>VH Posting System</h1>
|
||
<div class="user-menu">
|
||
<button class="theme-toggle" id="theme-toggle" title="Переключить тему"></button>
|
||
<span class="username"><?= htmlspecialchars($currentUser) ?></span>
|
||
<a href="?logout=1" class="btn btn-small btn-secondary">Выход</a>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- Main Navigation -->
|
||
<nav class="main-nav">
|
||
<button class="nav-btn" data-tab="gallery">Галерея</button>
|
||
<button class="nav-btn active" data-tab="posting">Публикация</button>
|
||
<button class="nav-btn" data-tab="badge">Цифровой бейдж</button>
|
||
<button class="nav-btn" data-tab="converter">Конвертер</button>
|
||
<button class="nav-btn" data-tab="widget">Виджет</button>
|
||
<button class="nav-btn" data-tab="settings">Настройки</button>
|
||
</nav>
|
||
|
||
<!-- Tab: Flickr Gallery -->
|
||
<section id="tab-gallery" class="tab-content">
|
||
<div class="panel">
|
||
<!-- OAuth Status Banner -->
|
||
<div id="oauth-banner" class="oauth-banner hidden">
|
||
<div class="oauth-banner-content">
|
||
<span class="oauth-banner-icon">🔐</span>
|
||
<span class="oauth-banner-text">
|
||
<strong>Оригиналы недоступны</strong> — требуется авторизация Flickr
|
||
</span>
|
||
<a href="flickr_auth.php" class="btn btn-small btn-primary">Авторизовать</a>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Albums View (default) -->
|
||
<div id="albums-view" class="gallery-view">
|
||
<div class="gallery-header">
|
||
<h2>Альбомы Flickr</h2>
|
||
<div class="gallery-toolbar">
|
||
<button id="btn-load-albums" class="btn btn-secondary">
|
||
<span class="btn-icon-text">↻</span> Обновить
|
||
</button>
|
||
<div class="toolbar-search">
|
||
<input type="text" id="search-albums" placeholder="Поиск альбомов...">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<p class="drag-hint" id="drag-hint">💡 Перетащите альбомы для изменения порядка</p>
|
||
|
||
<div id="albums-grid" class="albums-grid">
|
||
<p class="placeholder">Загрузка альбомов...</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Photos View (when inside album) -->
|
||
<div id="photos-view" class="gallery-view hidden">
|
||
<div class="gallery-header">
|
||
<div class="breadcrumb">
|
||
<button id="btn-back-to-albums" class="btn btn-text">
|
||
← Назад к альбомам
|
||
</button>
|
||
<span class="breadcrumb-separator">/</span>
|
||
<span id="current-album-title" class="breadcrumb-current">Альбом</span>
|
||
</div>
|
||
<div class="gallery-toolbar">
|
||
<div class="toolbar-search">
|
||
<input type="text" id="search-photos" placeholder="Поиск фото...">
|
||
</div>
|
||
<span id="photos-count" class="photos-count"></span>
|
||
<button id="btn-download-album" class="btn btn-small" onclick="downloadAllPhotos()" title="Скачать все фото альбома">
|
||
↓ Скачать альбом
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Photo Grid -->
|
||
<div id="photo-gallery" class="photo-gallery">
|
||
<p class="placeholder">Загрузка фотографий...</p>
|
||
</div>
|
||
|
||
<!-- Pagination -->
|
||
<div class="pagination">
|
||
<button id="btn-prev-page" class="btn btn-small" disabled>←</button>
|
||
<span id="page-info">1</span>
|
||
<button id="btn-next-page" class="btn btn-small" disabled>→</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Floating Action Bar (appears when photos selected) -->
|
||
<div id="selection-bar" class="floating-action-bar hidden">
|
||
<div class="action-bar-left">
|
||
<span id="selected-count" class="selection-count">0</span>
|
||
<span class="selection-label">выбрано</span>
|
||
</div>
|
||
<div class="action-bar-center">
|
||
<button id="btn-select-all" class="action-btn" title="Выбрать все">
|
||
<span class="action-icon">☑</span>
|
||
<span class="action-text">Все</span>
|
||
</button>
|
||
<button id="btn-deselect-all" class="action-btn" title="Снять выбор">
|
||
<span class="action-icon">☐</span>
|
||
<span class="action-text">Сброс</span>
|
||
</button>
|
||
</div>
|
||
<div class="action-bar-right">
|
||
<button id="btn-download-selected" class="action-btn" title="Скачать оригиналы">
|
||
<span class="action-icon">↓</span>
|
||
<span class="action-text">Скачать</span>
|
||
</button>
|
||
<button id="btn-convert-selected" class="action-btn action-secondary" title="Конвертировать в BBCode/HTML">
|
||
<span class="action-icon">{ }</span>
|
||
<span class="action-text">Код</span>
|
||
</button>
|
||
<button id="btn-telegram-selected" class="action-btn action-primary" title="Опубликовать в соцсети">
|
||
<span class="action-icon">↗</span>
|
||
<span class="action-text">Опубликовать</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Tab: Multi-Platform Posting -->
|
||
<section id="tab-posting" class="tab-content active">
|
||
<div class="panel">
|
||
<h2>Публикация в социальные сети</h2>
|
||
<p class="help-text">Выберите платформы и опубликуйте одним нажатием</p>
|
||
|
||
<!-- Photos Section -->
|
||
<div class="form-group">
|
||
<label>Фото и видео: <span id="photo-counter" class="photo-counter">0/9</span></label>
|
||
<div class="photo-source-buttons">
|
||
<button type="button" class="btn btn-secondary" id="btn-select-from-flickr">
|
||
<span class="btn-icon-text">🖼</span> Выбрать с Flickr
|
||
</button>
|
||
<input type="file" id="file-upload" multiple accept="image/*,video/*" style="display: none;">
|
||
<button type="button" class="btn btn-secondary" id="btn-upload-files">
|
||
<span class="btn-icon-text">📤</span> Загрузить
|
||
</button>
|
||
</div>
|
||
<div id="post-photos-preview" class="photos-preview combined-preview">
|
||
<p class="placeholder">Нажмите кнопку выше чтобы добавить фото</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Post Text -->
|
||
<div class="form-group">
|
||
<label for="post-text">Текст публикации:</label>
|
||
<div class="text-editor">
|
||
<div class="editor-toolbar">
|
||
<button type="button" class="toolbar-btn" data-format="bold" title="Жирный"><b>B</b></button>
|
||
<button type="button" class="toolbar-btn" data-format="italic" title="Курсив"><i>I</i></button>
|
||
<button type="button" class="toolbar-btn" data-format="underline" title="Подчёркнутый"><u>U</u></button>
|
||
<button type="button" class="toolbar-btn" data-format="strike" title="Зачёркнутый"><s>S</s></button>
|
||
<span class="toolbar-separator"></span>
|
||
<button type="button" class="toolbar-btn" data-format="link" title="Ссылка">🔗</button>
|
||
<button type="button" class="toolbar-btn" data-format="code" title="Код"></></button>
|
||
</div>
|
||
<textarea id="post-text" rows="4" placeholder="Введите текст для публикации..."></textarea>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tags -->
|
||
<div class="form-group">
|
||
<label>Теги:</label>
|
||
<div class="tags-container" id="post-tags-container">
|
||
<div class="tags-list" id="post-tags-list"></div>
|
||
<div class="tags-input-wrapper">
|
||
<input type="text" id="post-tags-input" class="tags-input" placeholder="Добавить тег...">
|
||
<div class="tags-suggestions" id="post-tags-suggestions"></div>
|
||
</div>
|
||
</div>
|
||
<div class="tags-presets">
|
||
<span class="tags-presets-label">Быстрые теги:</span>
|
||
<div class="presets-list" id="post-presets-list"></div>
|
||
<button type="button" class="preset-add-btn" id="post-preset-add" title="Добавить пресет">+</button>
|
||
<button type="button" class="preset-manage-btn" id="post-preset-manage" title="Управление пресетами">⚙</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Platform Selection -->
|
||
<div class="form-group">
|
||
<label>Выберите платформы:</label>
|
||
<div class="platforms-grid">
|
||
<!-- Telegram -->
|
||
<div class="platform-card">
|
||
<div class="platform-header">
|
||
<label class="platform-checkbox">
|
||
<input type="checkbox" id="chk-telegram" checked>
|
||
<span class="checkmark"></span>
|
||
</label>
|
||
<div class="platform-info">
|
||
<span class="platform-name">Telegram</span>
|
||
<span id="tg-status-mini" class="status-mini">Не подключён</span>
|
||
</div>
|
||
</div>
|
||
<select id="tg-channel" class="platform-target">
|
||
<option value="">Выберите канал...</option>
|
||
</select>
|
||
</div>
|
||
|
||
<!-- VK -->
|
||
<div class="platform-card">
|
||
<div class="platform-header">
|
||
<label class="platform-checkbox">
|
||
<input type="checkbox" id="chk-vk" checked>
|
||
<span class="checkmark"></span>
|
||
</label>
|
||
<div class="platform-info">
|
||
<span class="platform-name">ВКонтакте</span>
|
||
<span id="vk-status-mini" class="status-mini">Не подключён</span>
|
||
</div>
|
||
</div>
|
||
<select id="vk-group" class="platform-target">
|
||
<option value="">Выберите группу...</option>
|
||
</select>
|
||
</div>
|
||
|
||
<!-- Instagram (info only) -->
|
||
<div class="platform-card platform-disabled">
|
||
<div class="platform-header">
|
||
<label class="platform-checkbox">
|
||
<input type="checkbox" id="chk-instagram" disabled>
|
||
<span class="checkmark"></span>
|
||
</label>
|
||
<div class="platform-info">
|
||
<span class="platform-name">Instagram</span>
|
||
<span class="status-mini">Требует настройки</span>
|
||
</div>
|
||
</div>
|
||
<p class="platform-note">Требуется Facebook Business API</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Post Options -->
|
||
<div class="post-options-grid">
|
||
<div class="form-group">
|
||
<label for="post-parse-mode">Формат:</label>
|
||
<select id="post-parse-mode">
|
||
<option value="HTML">HTML</option>
|
||
<option value="Markdown">Markdown</option>
|
||
<option value="">Текст</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="checkbox-label compact">
|
||
<input type="checkbox" id="chk-cross-promo" checked>
|
||
<span>Кросс-промо</span>
|
||
</label>
|
||
</div>
|
||
<div class="form-group schedule-toggle">
|
||
<label class="checkbox-label compact">
|
||
<input type="checkbox" id="chk-schedule">
|
||
<span>Отложить</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Schedule Options (hidden by default) -->
|
||
<div id="schedule-options" class="schedule-options hidden">
|
||
<label class="schedule-label">📅 Когда опубликовать:</label>
|
||
<div class="schedule-presets">
|
||
<button type="button" class="preset-btn" data-preset="1h">Через 1 час</button>
|
||
<button type="button" class="preset-btn" data-preset="3h">Через 3 часа</button>
|
||
<button type="button" class="preset-btn" data-preset="tomorrow-10">Завтра 10:00</button>
|
||
<button type="button" class="preset-btn" data-preset="tomorrow-18">Завтра 18:00</button>
|
||
</div>
|
||
<div class="schedule-custom">
|
||
<div class="schedule-date-row">
|
||
<div class="schedule-field">
|
||
<label for="schedule-date">Дата:</label>
|
||
<input type="date" id="schedule-date" class="schedule-input">
|
||
</div>
|
||
<div class="schedule-field">
|
||
<label for="schedule-time">Время:</label>
|
||
<input type="time" id="schedule-time" class="schedule-input">
|
||
</div>
|
||
</div>
|
||
<input type="hidden" id="scheduled-datetime">
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Action Buttons -->
|
||
<div class="post-actions">
|
||
<button id="btn-send-post" class="btn btn-primary btn-large">
|
||
🚀 Опубликовать
|
||
</button>
|
||
</div>
|
||
|
||
<div id="post-result" class="result-message"></div>
|
||
|
||
<!-- Scheduled Posts List -->
|
||
<div class="scheduled-section">
|
||
<div class="scheduled-header">
|
||
<h3>📅 Отложенные публикации</h3>
|
||
<span id="scheduled-count" class="badge">0</span>
|
||
</div>
|
||
<div id="scheduled-posts-list" class="scheduled-posts-list">
|
||
<p class="placeholder">Нет запланированных постов</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Published Posts Archive -->
|
||
<div class="archive-section">
|
||
<div class="archive-header">
|
||
<h3>✓ Архив публикаций</h3>
|
||
<button type="button" class="btn btn-small btn-secondary" id="btn-refresh-archive">↻</button>
|
||
</div>
|
||
<div id="published-posts-list" class="published-posts-list">
|
||
<p class="placeholder">Загрузка...</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Tab: Link Converter -->
|
||
<section id="tab-converter" class="tab-content">
|
||
<div class="panel">
|
||
<h2>Конвертер ссылок Flickr</h2>
|
||
<p class="help-text">Преобразование ссылок в различные форматы для форумов и соцсетей</p>
|
||
|
||
<div class="converter-grid">
|
||
<!-- Left Column: Input -->
|
||
<div class="converter-input-section">
|
||
<div class="form-group">
|
||
<label for="input-urls">Ссылки Flickr:</label>
|
||
<textarea id="input-urls" rows="5" placeholder="https://www.flickr.com/photos/username/12345678901/
|
||
https://flic.kr/p/ABC123
|
||
https://live.staticflickr.com/65535/12345678901_abcdef1234_b.jpg"></textarea>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label for="image-size">Размер:</label>
|
||
<select id="image-size">
|
||
<option value="Large" selected>Большой (1024px)</option>
|
||
<option value="Large1600">1600px</option>
|
||
<option value="Large2048">2048px</option>
|
||
<option value="Original">Оригинал</option>
|
||
<option value="Medium640">640px</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="output-format">Формат:</label>
|
||
<select id="output-format">
|
||
<optgroup label="Кукольные форумы">
|
||
<option value="bjdclub">BJDClub.ru</option>
|
||
<option value="babiki">Babiki.ru</option>
|
||
<option value="babiki_simple">Babiki (простой)</option>
|
||
<option value="doll_forum">Универсальный</option>
|
||
</optgroup>
|
||
<optgroup label="BBCode">
|
||
<option value="bbcode">BBCode</option>
|
||
<option value="bbcode_linked">BBCode + ссылка</option>
|
||
</optgroup>
|
||
<optgroup label="HTML / Markdown">
|
||
<option value="html">HTML</option>
|
||
<option value="markdown">Markdown</option>
|
||
<option value="url_only">Только URL</option>
|
||
</optgroup>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<button id="btn-convert" class="btn btn-primary btn-block">Конвертировать</button>
|
||
</div>
|
||
|
||
<!-- Right Column: Text & Tags -->
|
||
<div class="converter-text-section">
|
||
<div class="form-group">
|
||
<label for="converter-title">Заголовок:</label>
|
||
<input type="text" id="converter-title" placeholder="Название поста...">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="converter-text">Текст к фотографиям:</label>
|
||
<div class="text-editor">
|
||
<div class="editor-toolbar">
|
||
<button type="button" class="toolbar-btn" data-format="bold" data-target="converter-text" title="Жирный"><b>B</b></button>
|
||
<button type="button" class="toolbar-btn" data-format="italic" data-target="converter-text" title="Курсив"><i>I</i></button>
|
||
<button type="button" class="toolbar-btn" data-format="underline" data-target="converter-text" title="Подчёркнутый"><u>U</u></button>
|
||
<span class="toolbar-separator"></span>
|
||
<button type="button" class="toolbar-btn" data-format="link" data-target="converter-text" title="Ссылка">🔗</button>
|
||
</div>
|
||
<textarea id="converter-text" rows="3" placeholder="Описание, комментарии к фото..."></textarea>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Теги:</label>
|
||
<div class="tags-container" id="converter-tags-container">
|
||
<div class="tags-list" id="converter-tags-list"></div>
|
||
<div class="tags-input-wrapper">
|
||
<input type="text" id="converter-tags-input" class="tags-input" placeholder="Добавить тег...">
|
||
<div class="tags-suggestions" id="converter-tags-suggestions"></div>
|
||
</div>
|
||
</div>
|
||
<div class="tags-presets">
|
||
<span class="tags-presets-label">Быстрые:</span>
|
||
<div class="presets-list" id="converter-presets-list"></div>
|
||
<button type="button" class="preset-add-btn" id="converter-preset-add" title="Добавить пресет">+</button>
|
||
<button type="button" class="preset-manage-btn" id="converter-preset-manage" title="Управление пресетами">⚙</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Output Section -->
|
||
<div class="converter-output-section">
|
||
<div class="form-group">
|
||
<label for="output-result">Результат:</label>
|
||
<textarea id="output-result" rows="6" readonly placeholder="Результат появится здесь после конвертации..."></textarea>
|
||
<div class="output-actions">
|
||
<button id="btn-copy" class="btn btn-secondary">📋 Скопировать</button>
|
||
<button id="btn-copy-with-tags" class="btn btn-secondary">📋 С тегами</button>
|
||
<span id="copy-status" class="copy-status"></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Tab: Widget Settings -->
|
||
<section id="tab-widget" class="tab-content">
|
||
<div class="panel">
|
||
<h2>Виджет для WordPress</h2>
|
||
<p class="help-text">Настройте мозаику фотографий для отображения на вашем сайте</p>
|
||
|
||
<!-- Widget Status -->
|
||
<div class="settings-section">
|
||
<h3>Статус виджета</h3>
|
||
<div class="form-group">
|
||
<label class="checkbox-label">
|
||
<input type="checkbox" id="widget-enabled" checked>
|
||
<span>Виджет включён</span>
|
||
</label>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>API URL для WordPress:</label>
|
||
<input type="text" id="widget-api-url" readonly>
|
||
<button type="button" class="btn btn-small btn-secondary" onclick="copyWidgetUrl()">Копировать</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Album Selection -->
|
||
<div class="settings-section">
|
||
<h3>Выбор альбомов</h3>
|
||
<p class="help-text">Выберите альбомы для отображения в виджете. Если ничего не выбрано — показываются последние фото.</p>
|
||
<div class="form-group">
|
||
<button type="button" id="btn-load-widget-albums" class="btn btn-secondary">Загрузить альбомы</button>
|
||
</div>
|
||
<div id="widget-albums-list" class="widget-albums-grid">
|
||
<p class="placeholder">Нажмите «Загрузить альбомы» для выбора</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Widget Options -->
|
||
<div class="settings-section">
|
||
<h3>Параметры</h3>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label for="widget-max-photos">Максимум фото:</label>
|
||
<input type="number" id="widget-max-photos" value="30" min="5" max="100">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="widget-cache-time">Кэш (секунд):</label>
|
||
<input type="number" id="widget-cache-time" value="3600" min="60" max="86400">
|
||
<span class="hint">3600 = 1 час</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<button id="btn-save-widget-settings" class="btn btn-primary btn-large">Сохранить настройки виджета</button>
|
||
<span id="widget-save-status" class="save-status"></span>
|
||
|
||
<!-- WordPress Installation -->
|
||
<div class="settings-section" style="margin-top: 32px;">
|
||
<h3>Установка на WordPress</h3>
|
||
<div class="code-block">
|
||
<p>1. Скачайте плагин:</p>
|
||
<a href="vh-flickr-mosaic.zip" class="btn btn-secondary" id="download-plugin-btn">Скачать плагин</a>
|
||
</div>
|
||
<div class="code-block">
|
||
<p>2. Установите плагин в WordPress (Плагины → Добавить новый → Загрузить)</p>
|
||
</div>
|
||
<div class="code-block">
|
||
<p>3. В настройках плагина укажите API URL:</p>
|
||
<code id="widget-api-url-code"></code>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Tab: Digital Badge (round display) -->
|
||
<section id="tab-badge" class="tab-content">
|
||
<div class="panel">
|
||
<h2>Цифровой бейдж</h2>
|
||
<p class="help-text">Подготовьте фотографии под круглые дисплеи 240×240 пикселей. Кадрируйте, добавьте ценник или подпись никнеймом дугой снизу.</p>
|
||
|
||
<!-- Photo sources -->
|
||
<div class="form-group">
|
||
<label>Источник фотографий:</label>
|
||
<div class="photo-source-buttons">
|
||
<input type="file" id="badge-file-upload" multiple accept="image/*" style="display: none;">
|
||
<button type="button" class="btn btn-secondary" id="btn-badge-upload">
|
||
<span class="btn-icon-text">📤</span> Загрузить с устройства
|
||
</button>
|
||
<button type="button" class="btn btn-secondary" id="btn-badge-from-flickr">
|
||
<span class="btn-icon-text">🖼</span> Выбранные в галерее Flickr
|
||
</button>
|
||
</div>
|
||
<p class="hint">Размер итогового кружка — 240×240 PNG с прозрачным фоном.</p>
|
||
</div>
|
||
|
||
<!-- Items grid (selected photos for badge processing) -->
|
||
<div class="form-group">
|
||
<label>Фотографии для бейджа: <span id="badge-items-count" class="photo-counter">0</span></label>
|
||
<div id="badge-items-grid" class="badge-items-grid">
|
||
<p class="placeholder">Выберите фото из галереи или загрузите с устройства</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Editor (visible when a photo is selected) -->
|
||
<div id="badge-editor" class="badge-editor hidden">
|
||
<div class="badge-editor-layout">
|
||
<!-- Canvas preview -->
|
||
<div class="badge-canvas-wrapper">
|
||
<canvas id="badge-canvas" width="480" height="480"></canvas>
|
||
<div class="badge-canvas-hint">Один палец — сдвиг, два пальца — масштаб. На компьютере — мышь и колесо.</div>
|
||
</div>
|
||
|
||
<!-- Controls -->
|
||
<div class="badge-controls">
|
||
<div class="form-group">
|
||
<label>Масштаб: <span id="badge-zoom-value">100%</span></label>
|
||
<input type="range" id="badge-zoom" min="100" max="400" step="1" value="100">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<button type="button" id="btn-badge-reset" class="btn btn-secondary btn-small">Сбросить кадрирование</button>
|
||
</div>
|
||
|
||
<hr style="border:none; border-top:1px solid var(--border-color, #ddd); margin:12px 0;">
|
||
|
||
<!-- Price tag -->
|
||
<div class="form-group">
|
||
<label class="checkbox-label">
|
||
<input type="checkbox" id="badge-price-enabled">
|
||
<span>Добавить ценник</span>
|
||
</label>
|
||
</div>
|
||
<div id="badge-price-options" class="badge-sub-options hidden">
|
||
<div class="form-group">
|
||
<label for="badge-price-value">Цена (₽):</label>
|
||
<input type="number" inputmode="numeric" pattern="[0-9]*" id="badge-price-value" min="0" step="1" placeholder="2500">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Стиль плашки:</label>
|
||
<div class="badge-radio-row">
|
||
<label class="radio-label">
|
||
<input type="radio" name="badge-price-style" value="arc" checked>
|
||
<span>Дуга по низу</span>
|
||
</label>
|
||
<label class="radio-label">
|
||
<input type="radio" name="badge-price-style" value="rect">
|
||
<span>Прямоугольник</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<hr style="border:none; border-top:1px solid var(--border-color, #ddd); margin:12px 0;">
|
||
|
||
<!-- Nickname -->
|
||
<div class="form-group">
|
||
<label class="checkbox-label">
|
||
<input type="checkbox" id="badge-nickname-enabled">
|
||
<span>Добавить никнейм (дугой снизу)</span>
|
||
</label>
|
||
</div>
|
||
<div id="badge-nickname-options" class="badge-sub-options hidden">
|
||
<div class="form-group">
|
||
<label for="badge-nickname-value">Текст:</label>
|
||
<input type="text" id="badge-nickname-value" maxlength="40" placeholder="Никнейм">
|
||
<span class="hint">По умолчанию — из настроек.</span>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Цвет текста:</label>
|
||
<div class="badge-radio-row">
|
||
<label class="radio-label">
|
||
<input type="radio" name="badge-nick-color" value="white" checked>
|
||
<span>Белый (с тенью)</span>
|
||
</label>
|
||
<label class="radio-label">
|
||
<input type="radio" name="badge-nick-color" value="black">
|
||
<span>Чёрный</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<hr style="border:none; border-top:1px solid var(--border-color, #ddd); margin:12px 0;">
|
||
|
||
<div class="badge-actions">
|
||
<button type="button" id="btn-badge-download" class="btn btn-primary">
|
||
<span class="btn-icon-text">↓</span> <span id="badge-download-label">Сохранить PNG</span>
|
||
</button>
|
||
<button type="button" id="btn-badge-remove" class="btn btn-secondary btn-small">Убрать из списка</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Batch actions -->
|
||
<div id="badge-batch-actions" class="form-group hidden" style="margin-top: 16px;">
|
||
<button type="button" id="btn-badge-download-all" class="btn btn-secondary">
|
||
<span class="btn-icon-text">⬇</span> Скачать все бейджи
|
||
</button>
|
||
</div>
|
||
|
||
<!-- History -->
|
||
<div class="settings-section" style="margin-top: 24px;">
|
||
<h3>История сохранённых бейджей</h3>
|
||
<div id="badge-history-grid" class="badge-history-grid">
|
||
<p class="placeholder">История пуста — сгенерированные бейджи будут появляться здесь.</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Tab: Settings -->
|
||
<section id="tab-settings" class="tab-content">
|
||
<div class="panel">
|
||
<h2>Настройки</h2>
|
||
|
||
<!-- Flickr Settings -->
|
||
<div class="settings-section">
|
||
<h3>Flickr API</h3>
|
||
<div class="form-group">
|
||
<label>API ключ:</label>
|
||
<input type="text" value="<?= !empty($config['flickr']['api_key']) ? '••••••••' . substr($config['flickr']['api_key'], -4) : '' ?>" readonly>
|
||
<span class="status <?= !empty($config['flickr']['api_key']) ? 'connected' : 'disconnected' ?>">
|
||
<?= !empty($config['flickr']['api_key']) ? 'Настроен' : 'Не настроен' ?>
|
||
</span>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>ID пользователя:</label>
|
||
<input type="text" value="<?= htmlspecialchars($config['flickr_user_id'] ?? '') ?>" readonly>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>OAuth авторизация:</label>
|
||
<span id="flickr-oauth-status" class="status">Проверка...</span>
|
||
<a href="flickr_auth.php" id="flickr-oauth-btn" class="btn btn-small btn-primary" style="margin-left: 10px;">Авторизовать</a>
|
||
<span class="hint">Требуется для загрузки оригиналов</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Telegram Settings -->
|
||
<div class="settings-section">
|
||
<h3>Telegram</h3>
|
||
<div class="form-group">
|
||
<label>Статус бота:</label>
|
||
<span id="tg-bot-status" class="status">Проверка...</span>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="tg-channels-list">Каналы (по одному на строку):</label>
|
||
<textarea id="tg-channels-list" rows="3" placeholder="@channel_username
|
||
-1001234567890"><?php
|
||
$channels = $config['telegram']['channels'] ?? [];
|
||
foreach ($channels as $ch) {
|
||
echo htmlspecialchars($ch['id'] ?? $ch) . "\n";
|
||
}
|
||
?></textarea>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- VK Settings -->
|
||
<div class="settings-section">
|
||
<h3>ВКонтакте</h3>
|
||
<div class="form-group">
|
||
<label>Статус:</label>
|
||
<span id="vk-status" class="status">Проверка...</span>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="vk-token-input">Access Token:</label>
|
||
<input type="password" id="vk-token-input" placeholder="Вставьте токен сюда..." value="<?= !empty($config['vk']['access_token']) ? $config['vk']['access_token'] : '' ?>">
|
||
<button id="btn-save-vk-token" class="btn btn-primary btn-small" style="margin-left: 10px;">Сохранить</button>
|
||
<button id="btn-toggle-vk-token" class="btn btn-secondary btn-small" style="margin-left: 5px;">👁</button>
|
||
</div>
|
||
<div class="form-group">
|
||
<span id="vk-token-save-status" class="save-status"></span>
|
||
</div>
|
||
<div class="form-group">
|
||
<details class="vk-help" open>
|
||
<summary>Как получить access token для VK?</summary>
|
||
<p style="font-size: 0.9em; margin: 10px 0;"><strong>VK API имеет ограничение:</strong> загружать фото на стену может только <em>пользовательский</em> токен. Токен сообщества постит только текст (фото уйдут как ссылки).</p>
|
||
|
||
<p style="font-size: 0.92em; margin: 12px 0 6px;"><strong>Вариант A — пользовательский токен через Kate Mobile (рекомендуется, фото работают)</strong></p>
|
||
<ol style="margin: 6px 0 10px; padding-left: 20px; font-size: 0.9em;">
|
||
<li>Перейдите по ссылке (откроется страница авторизации VK): <br>
|
||
<a href="https://oauth.vk.com/authorize?client_id=2685278&scope=photos,wall,groups,offline&redirect_uri=https://oauth.vk.com/blank.html&display=mobile&response_type=token&revoke=1&v=5.199" target="_blank" rel="noopener" style="word-break: break-all;">oauth.vk.com/authorize?client_id=2685278&scope=photos,wall,groups,offline&...</a>
|
||
</li>
|
||
<li>Нажмите <strong>«Разрешить»</strong> от лица Kate Mobile</li>
|
||
<li>В адресной строке после <code>#</code> найдите <code>access_token=...</code> — скопируйте значение до символа <code>&</code></li>
|
||
<li>Вставьте в поле выше → «Сохранить»</li>
|
||
</ol>
|
||
<p style="font-size: 0.82em; color: var(--text-secondary); margin: 6px 0 14px;">
|
||
Скоуп <code>offline</code> делает токен бессрочным. Kate Mobile (<code>app_id=2685278</code>) пока не заблокирован VK, в отличие от VK Admin.
|
||
</p>
|
||
|
||
<p style="font-size: 0.92em; margin: 12px 0 6px;"><strong>Вариант B — токен сообщества (только текст, без фото)</strong></p>
|
||
<ol style="margin: 6px 0 10px; padding-left: 20px; font-size: 0.9em;">
|
||
<li>Группа VK → <strong>Управление</strong> → <strong>Работа с API</strong> → <strong>Создать ключ</strong></li>
|
||
<li>Права: Управление, Стена, Фотографии, Сообщения сообщества</li>
|
||
<li>Вставьте ключ в поле выше → «Сохранить»</li>
|
||
</ol>
|
||
<p style="font-size: 0.82em; color: var(--text-secondary); margin-top: 6px;">
|
||
Бессрочный, но из-за ограничения VK <code>photos.getWallUploadServer</code> не работает с такими токенами — фото уходят ссылками в тексте.
|
||
</p>
|
||
<p style="font-size: 0.78em; color: var(--text-secondary); margin-top: 10px;">
|
||
Старый способ через <a href="https://vkhost.github.io/" target="_blank" rel="noopener">vkhost.github.io</a> (VK Admin) больше не работает — VK заблокировал это приложение (<code>[8] Application is blocked</code>).
|
||
</p>
|
||
</details>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Cross-Promo Settings -->
|
||
<div class="settings-section">
|
||
<h3>Кросс-промо каналов</h3>
|
||
<p class="help-text">Ссылки на ваши каналы для автоматического добавления в посты</p>
|
||
<div class="form-group">
|
||
<label for="cross-promo-telegram">Ссылка на Telegram канал:</label>
|
||
<input type="text" id="cross-promo-telegram" placeholder="https://t.me/your_channel">
|
||
<span class="hint">Будет добавлена в посты VK</span>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="cross-promo-vk">Ссылка на ВКонтакте:</label>
|
||
<input type="text" id="cross-promo-vk" placeholder="https://vk.com/your_group">
|
||
<span class="hint">Будет добавлена в посты Telegram</span>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="cross-promo-text-tg">Текст для Telegram:</label>
|
||
<input type="text" id="cross-promo-text-tg" placeholder="Мой канал ВКонтакте" value="Мой канал ВКонтакте">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="cross-promo-text-vk">Текст для ВКонтакте:</label>
|
||
<input type="text" id="cross-promo-text-vk" placeholder="Мой канал в Telegram" value="Мой канал в Telegram">
|
||
</div>
|
||
<button id="btn-save-cross-promo" class="btn btn-primary">Сохранить настройки кросс-промо</button>
|
||
<span id="cross-promo-save-status" class="save-status"></span>
|
||
</div>
|
||
|
||
<!-- Digital Badge Settings -->
|
||
<div class="settings-section">
|
||
<h3>Цифровой бейдж</h3>
|
||
<p class="help-text">Никнейм по умолчанию для подписи дугой снизу.</p>
|
||
<div class="form-group">
|
||
<label for="badge-nickname-default">Никнейм для бейджа:</label>
|
||
<input type="text" id="badge-nickname-default" maxlength="40" placeholder="Ваш никнейм">
|
||
<button id="btn-save-badge-nickname" class="btn btn-primary btn-small" style="margin-left: 10px;">Сохранить</button>
|
||
<span id="badge-nickname-save-status" class="save-status"></span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Theme -->
|
||
<div class="settings-section">
|
||
<h3>Оформление</h3>
|
||
<div class="form-group">
|
||
<label>Тема интерфейса:</label>
|
||
<button id="btn-toggle-theme" class="btn btn-secondary">Переключить тему</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Password Change -->
|
||
<div class="settings-section">
|
||
<h3>Смена пароля</h3>
|
||
<div class="form-group">
|
||
<label for="current-password">Текущий пароль:</label>
|
||
<input type="password" id="current-password">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="new-password">Новый пароль:</label>
|
||
<input type="password" id="new-password">
|
||
<span class="hint">Минимум 8 символов</span>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="confirm-password">Подтвердите пароль:</label>
|
||
<input type="password" id="confirm-password">
|
||
</div>
|
||
<button id="btn-change-password" class="btn btn-primary">Сменить пароль</button>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
|
||
<!-- Preset Management Modal -->
|
||
<div id="preset-modal" class="modal-overlay" style="display: none;">
|
||
<div class="modal-content preset-modal">
|
||
<div class="modal-header">
|
||
<h3 id="preset-modal-title">Управление пресетами</h3>
|
||
<button type="button" class="modal-close" id="preset-modal-close">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<!-- Add/Edit Form -->
|
||
<div id="preset-form-section" style="display: none;">
|
||
<div class="form-group">
|
||
<label for="preset-name">Название пресета:</label>
|
||
<input type="text" id="preset-name" class="form-control" placeholder="Например: BJD">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="preset-tags">Теги (через запятую):</label>
|
||
<input type="text" id="preset-tags" class="form-control" placeholder="bjd, doll, куклы">
|
||
</div>
|
||
<div class="preset-form-actions">
|
||
<button type="button" id="preset-save" class="btn btn-primary">Сохранить</button>
|
||
<button type="button" id="preset-cancel" class="btn btn-secondary">Отмена</button>
|
||
</div>
|
||
</div>
|
||
<!-- Presets List -->
|
||
<div id="preset-list-section">
|
||
<div class="preset-manager-list" id="preset-manager-list"></div>
|
||
<button type="button" id="preset-add-new" class="btn btn-primary btn-block">+ Добавить пресет</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="js/app.js?v=<?= filemtime(__DIR__ . '/js/app.js') ?>"></script>
|
||
</body>
|
||
</html>
|