1119 lines
44 KiB
PHP
Executable File
1119 lines
44 KiB
PHP
Executable File
<?php
|
||
/**
|
||
* Beep Settings
|
||
*
|
||
* Handles all customizable settings for the Beep plugin.
|
||
* Settings are stored in wp_options and injected as CSS variables.
|
||
*/
|
||
|
||
if (!defined("ABSPATH")) {
|
||
exit;
|
||
}
|
||
|
||
class Beep_Settings {
|
||
|
||
/**
|
||
* Option key for storing settings
|
||
*/
|
||
const OPTION_KEY = 'beep_settings';
|
||
|
||
/**
|
||
* Default settings
|
||
*/
|
||
private static $defaults = [
|
||
// Colors - Backgrounds
|
||
'bg_main' => '#FFFFFF', // Twitter Light: white
|
||
'bg_card' => '#F5F8FA', // Twitter Light: card background
|
||
'bg_card_hover' => '#EBEEF0', // Twitter Light: hover state
|
||
'bg_input' => '#F5F8FA', // Twitter Light: input fields
|
||
|
||
// Colors - Text
|
||
'text_primary' => '#0F1419', // Twitter Light: main text (near black)
|
||
'text_secondary' => '#536471', // Twitter Light: muted text
|
||
'text_link' => '#1D9BF0', // Twitter Light: links (Twitter blue)
|
||
|
||
// Colors - Borders & Dividers
|
||
'border_color' => '#EFF3F4', // Twitter Light: borders and dividers
|
||
'border_hover' => '#CBD0D4', // Twitter Light: hover border
|
||
|
||
// Colors - Interactive
|
||
'color_like' => '#F91880', // Like heart (Twitter pink)
|
||
'color_repost' => '#00BA7C', // Repost green
|
||
'color_reply' => '#1D9BF0', // Reply blue
|
||
'color_quote' => '#1D9BF0', // Quote blue
|
||
'color_poll' => '#1D9BF0', // Poll blue
|
||
|
||
// Colors - Buttons
|
||
'btn_primary_bg' => '#1D9BF0', // Primary button background
|
||
'btn_primary_text' => '#FFFFFF', // Primary button text
|
||
'btn_secondary_bg' => '#2F3336', // Secondary button background
|
||
'btn_secondary_text' => '#E7E9EA', // Secondary button text
|
||
|
||
// Colors - Avatar Ring
|
||
'avatar_ring' => '#2F3336', // Avatar ring color
|
||
|
||
// Branding
|
||
'logo_url' => '', // Custom logo URL
|
||
'icon_url' => '', // Custom icon URL (favicon)
|
||
'site_name' => 'Beep', // Or 'X' or 'Twitter'
|
||
'tagline' => 'Messages from',
|
||
|
||
// Layout
|
||
'feed_max_width' => '600px', // Max width of feed
|
||
'feed_padding' => '16px', // Feed padding
|
||
'post_padding' => '14px 20px', // Post padding
|
||
'avatar_size' => '48px', // Avatar dimensions
|
||
'gap_between_posts' => '12px', // Gap between posts
|
||
|
||
// Border Radius
|
||
'radius_card' => '16px', // Card border radius
|
||
'radius_avatar' => '9999px', // Avatar circle
|
||
'radius_button' => '9999px', // Buttons/pills
|
||
'radius_input' => '4px', // Input fields
|
||
|
||
// Typography
|
||
'font_family' => '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
||
'font_size_base' => '15px', // Base font size
|
||
'font_size_small' => '13px', // Small text (timestamps)
|
||
'font_size_large' => '20px', // Large text (names)
|
||
|
||
// Features (on/off)
|
||
'enable_likes' => '1',
|
||
'enable_reposts' => '1',
|
||
'enable_quotes' => '1',
|
||
'enable_polls' => '1',
|
||
'enable_follows' => '1',
|
||
'enable_media' => '1',
|
||
'enable_replies' => '1',
|
||
|
||
// Behavior
|
||
'character_limit' => '280',
|
||
'posts_per_page' => '20',
|
||
'auto_refresh' => '0', // Auto refresh interval (0 = off)
|
||
'public_posting' => '0', // Allow public posting without login
|
||
|
||
// Content
|
||
'show_avatar' => '1',
|
||
'show_stats' => '1', // Show reply/like counts
|
||
'show_timestamps' => '1', // Show relative time
|
||
'show_gravatar' => '1', // Show Gravatar
|
||
'show_public_notice' => '1', // Show "sign in to post" notice
|
||
|
||
// Animation
|
||
'animation_speed' => '150', // Transition speed in ms
|
||
];
|
||
|
||
/**
|
||
* Initialize settings
|
||
*/
|
||
public static function init() {
|
||
add_action('admin_init', [__CLASS__, 'register_settings']);
|
||
add_action('admin_menu', [__CLASS__, 'add_settings_page']);
|
||
add_filter('plugin_action_links_' . plugin_basename(BEEP_FILE), [__CLASS__, 'add_settings_link']);
|
||
add_action('wp_head', [__CLASS__, 'inject_css_variables'], 100);
|
||
}
|
||
|
||
/**
|
||
* Get all settings with defaults
|
||
*/
|
||
public static function get_settings() {
|
||
$saved = get_option(self::OPTION_KEY, []);
|
||
return wp_parse_args($saved, self::$defaults);
|
||
}
|
||
|
||
/**
|
||
* Get a single setting
|
||
*/
|
||
public static function get($key, $default = null) {
|
||
$settings = self::get_settings();
|
||
return isset($settings[$key]) ? $settings[$key] : ($default ?? (self::$defaults[$key] ?? null));
|
||
}
|
||
|
||
/**
|
||
* Register settings with WordPress
|
||
*/
|
||
public static function register_settings() {
|
||
register_setting(
|
||
self::OPTION_KEY,
|
||
self::OPTION_KEY,
|
||
[
|
||
'type' => 'array',
|
||
'sanitize_callback' => [__CLASS__, 'sanitize_settings'],
|
||
'default' => [],
|
||
]
|
||
);
|
||
|
||
// === Section: Colors - Backgrounds ===
|
||
add_settings_section(
|
||
'beep_colors_bg',
|
||
'Colors - Backgrounds',
|
||
[__CLASS__, 'section_colors_bg_description'],
|
||
'beep-settings'
|
||
);
|
||
|
||
add_settings_field(
|
||
'bg_main',
|
||
'Main Background',
|
||
[__CLASS__, 'field_color_picker'],
|
||
'beep-settings',
|
||
'beep_colors_bg',
|
||
['key' => 'bg_main', 'label' => 'Feed/Page background']
|
||
);
|
||
|
||
add_settings_field(
|
||
'bg_card',
|
||
'Card Background',
|
||
[__CLASS__, 'field_color_picker'],
|
||
'beep-settings',
|
||
'beep_colors_bg',
|
||
['key' => 'bg_card', 'label' => 'Post cards & containers']
|
||
);
|
||
|
||
add_settings_field(
|
||
'bg_card_hover',
|
||
'Card Hover Background',
|
||
[__CLASS__, 'field_color_picker'],
|
||
'beep-settings',
|
||
'beep_colors_bg',
|
||
['key' => 'bg_card_hover', 'label' => 'Hover state for cards']
|
||
);
|
||
|
||
add_settings_field(
|
||
'bg_input',
|
||
'Input Background',
|
||
[__CLASS__, 'field_color_picker'],
|
||
'beep-settings',
|
||
'beep_colors_bg',
|
||
['key' => 'bg_input', 'label' => 'Textarea/input fields']
|
||
);
|
||
|
||
// === Section: Colors - Text ===
|
||
add_settings_section(
|
||
'beep_colors_text',
|
||
'Colors - Text',
|
||
[__CLASS__, 'section_colors_text_description'],
|
||
'beep-settings'
|
||
);
|
||
|
||
add_settings_field(
|
||
'text_primary',
|
||
'Primary Text',
|
||
[__CLASS__, 'field_color_picker'],
|
||
'beep-settings',
|
||
'beep_colors_text',
|
||
['key' => 'text_primary', 'label' => 'Main text color']
|
||
);
|
||
|
||
add_settings_field(
|
||
'text_secondary',
|
||
'Secondary Text',
|
||
[__CLASS__, 'field_color_picker'],
|
||
'beep-settings',
|
||
'beep_colors_text',
|
||
['key' => 'text_secondary', 'label' => 'Muted/timestamp text']
|
||
);
|
||
|
||
add_settings_field(
|
||
'text_link',
|
||
'Link Color',
|
||
[__CLASS__, 'field_color_picker'],
|
||
'beep-settings',
|
||
'beep_colors_text',
|
||
['key' => 'text_link', 'label' => 'Links & buttons']
|
||
);
|
||
|
||
// === Section: Colors - Interactive ===
|
||
add_settings_section(
|
||
'beep_colors_interactive',
|
||
'Colors - Interactive Elements',
|
||
[__CLASS__, 'section_colors_interactive_description'],
|
||
'beep-settings'
|
||
);
|
||
|
||
add_settings_field(
|
||
'color_like',
|
||
'Like Heart Color',
|
||
[__CLASS__, 'field_color_picker'],
|
||
'beep-settings',
|
||
'beep_colors_interactive',
|
||
['key' => 'color_like', 'label' => 'Heart/like button']
|
||
);
|
||
|
||
add_settings_field(
|
||
'color_repost',
|
||
'Repost Color',
|
||
[__CLASS__, 'field_color_picker'],
|
||
'beep-settings',
|
||
'beep_colors_interactive',
|
||
['key' => 'color_repost', 'label' => 'Repost button']
|
||
);
|
||
|
||
add_settings_field(
|
||
'color_reply',
|
||
'Reply Color',
|
||
[__CLASS__, 'field_color_picker'],
|
||
'beep-settings',
|
||
'beep_colors_interactive',
|
||
['key' => 'color_reply', 'label' => 'Reply button']
|
||
);
|
||
|
||
add_settings_field(
|
||
'border_color',
|
||
'Border Color',
|
||
[__CLASS__, 'field_color_picker'],
|
||
'beep-settings',
|
||
'beep_colors_interactive',
|
||
['key' => 'border_color', 'label' => 'Dividers & borders']
|
||
);
|
||
|
||
// === Section: Layout ===
|
||
add_settings_section(
|
||
'beep_layout',
|
||
'Layout & Spacing',
|
||
[__CLASS__, 'section_layout_description'],
|
||
'beep-settings'
|
||
);
|
||
|
||
add_settings_field(
|
||
'feed_max_width',
|
||
'Feed Max Width',
|
||
[__CLASS__, 'field_text'],
|
||
'beep-settings',
|
||
'beep_layout',
|
||
['key' => 'feed_max_width', 'label' => 'e.g. 600px or 100%']
|
||
);
|
||
|
||
add_settings_field(
|
||
'feed_padding',
|
||
'Feed Padding',
|
||
[__CLASS__, 'field_text'],
|
||
'beep-settings',
|
||
'beep_layout',
|
||
['key' => 'feed_padding', 'label' => 'e.g. 16px']
|
||
);
|
||
|
||
add_settings_field(
|
||
'post_padding',
|
||
'Post Padding',
|
||
[__CLASS__, 'field_text'],
|
||
'beep-settings',
|
||
'beep_layout',
|
||
['key' => 'post_padding', 'label' => 'e.g. 14px 20px']
|
||
);
|
||
|
||
add_settings_field(
|
||
'avatar_size',
|
||
'Avatar Size',
|
||
[__CLASS__, 'field_text'],
|
||
'beep-settings',
|
||
'beep_layout',
|
||
['key' => 'avatar_size', 'label' => 'e.g. 48px']
|
||
);
|
||
|
||
// === Section: Border Radius ===
|
||
add_settings_section(
|
||
'beep_radius',
|
||
'Border Radius',
|
||
[__CLASS__, 'section_radius_description'],
|
||
'beep-settings'
|
||
);
|
||
|
||
add_settings_field(
|
||
'radius_card',
|
||
'Card Radius',
|
||
[__CLASS__, 'field_text'],
|
||
'beep-settings',
|
||
'beep_radius',
|
||
['key' => 'radius_card', 'label' => 'e.g. 16px for cards, 0 for sharp edges']
|
||
);
|
||
|
||
add_settings_field(
|
||
'radius_avatar',
|
||
'Avatar Radius',
|
||
[__CLASS__, 'field_text'],
|
||
'beep-settings',
|
||
'beep_radius',
|
||
['key' => 'radius_avatar', 'label' => 'e.g. 9999px for circle']
|
||
);
|
||
|
||
// === Section: Typography ===
|
||
add_settings_section(
|
||
'beep_typography',
|
||
'Typography',
|
||
[__CLASS__, 'section_typography_description'],
|
||
'beep-settings'
|
||
);
|
||
|
||
add_settings_field(
|
||
'font_family',
|
||
'Font Family',
|
||
[__CLASS__, 'field_text'],
|
||
'beep-settings',
|
||
'beep_typography',
|
||
['key' => 'font_family', 'label' => 'CSS font-family value']
|
||
);
|
||
|
||
add_settings_field(
|
||
'font_size_base',
|
||
'Base Font Size',
|
||
[__CLASS__, 'field_text'],
|
||
'beep-settings',
|
||
'beep_typography',
|
||
['key' => 'font_size_base', 'label' => 'e.g. 15px']
|
||
);
|
||
|
||
// === Section: Branding & Media ===
|
||
add_settings_section(
|
||
'beep_branding',
|
||
'Branding & Media',
|
||
[__CLASS__, 'section_branding_description'],
|
||
'beep-settings'
|
||
);
|
||
|
||
add_settings_field(
|
||
'logo_url',
|
||
'Logo Image',
|
||
[__CLASS__, 'field_media_upload'],
|
||
'beep-settings',
|
||
'beep_branding',
|
||
['key' => 'logo_url', 'label' => 'Upload a logo image. Leave empty to use text.']
|
||
);
|
||
|
||
add_settings_field(
|
||
'icon_url',
|
||
'Icon Image',
|
||
[__CLASS__, 'field_media_upload'],
|
||
'beep-settings',
|
||
'beep_branding',
|
||
['key' => 'icon_url', 'label' => 'Upload an icon/favicon image.']
|
||
);
|
||
|
||
add_settings_field(
|
||
'site_name',
|
||
'Site Name',
|
||
[__CLASS__, 'field_text'],
|
||
'beep-settings',
|
||
'beep_branding',
|
||
['key' => 'site_name', 'label' => 'e.g. Beep, X, or Twitter']
|
||
);
|
||
|
||
add_settings_field(
|
||
'tagline',
|
||
'Tagline',
|
||
[__CLASS__, 'field_text'],
|
||
'beep-settings',
|
||
'beep_branding',
|
||
['key' => 'tagline', 'label' => 'Text above logo, e.g. "Messages from"']
|
||
);
|
||
|
||
// === Section: Features ===
|
||
add_settings_section(
|
||
'beep_features',
|
||
'Features',
|
||
[__CLASS__, 'section_features_description'],
|
||
'beep-settings'
|
||
);
|
||
|
||
add_settings_field(
|
||
'enable_likes',
|
||
'Enable Likes',
|
||
[__CLASS__, 'field_checkbox'],
|
||
'beep-settings',
|
||
'beep_features',
|
||
['key' => 'enable_likes', 'label' => 'Show like button on posts']
|
||
);
|
||
|
||
add_settings_field(
|
||
'enable_reposts',
|
||
'Enable Reposts',
|
||
[__CLASS__, 'field_checkbox'],
|
||
'beep-settings',
|
||
'beep_features',
|
||
['key' => 'enable_reposts', 'label' => 'Show repost button']
|
||
);
|
||
|
||
add_settings_field(
|
||
'enable_quotes',
|
||
'Enable Quotes',
|
||
[__CLASS__, 'field_checkbox'],
|
||
'beep-settings',
|
||
'beep_features',
|
||
['key' => 'enable_quotes', 'label' => 'Show quote button']
|
||
);
|
||
|
||
add_settings_field(
|
||
'enable_replies',
|
||
'Enable Replies',
|
||
[__CLASS__, 'field_checkbox'],
|
||
'beep-settings',
|
||
'beep_features',
|
||
['key' => 'enable_replies', 'label' => 'Allow replying to posts']
|
||
);
|
||
|
||
add_settings_field(
|
||
'enable_media',
|
||
'Enable Media',
|
||
[__CLASS__, 'field_checkbox'],
|
||
'beep-settings',
|
||
'beep_features',
|
||
['key' => 'enable_media', 'label' => 'Allow image attachments']
|
||
);
|
||
|
||
add_settings_field(
|
||
'enable_polls',
|
||
'Enable Polls',
|
||
[__CLASS__, 'field_checkbox'],
|
||
'beep-settings',
|
||
'beep_features',
|
||
['key' => 'enable_polls', 'label' => 'Allow poll creation']
|
||
);
|
||
|
||
// === Section: Behavior ===
|
||
add_settings_section(
|
||
'beep_behavior',
|
||
'Behavior',
|
||
[__CLASS__, 'section_behavior_description'],
|
||
'beep-settings'
|
||
);
|
||
|
||
add_settings_field(
|
||
'character_limit',
|
||
'Character Limit',
|
||
[__CLASS__, 'field_text'],
|
||
'beep-settings',
|
||
'beep_behavior',
|
||
['key' => 'character_limit', 'label' => 'Max characters per post (default 280)']
|
||
);
|
||
|
||
add_settings_field(
|
||
'posts_per_page',
|
||
'Posts Per Page',
|
||
[__CLASS__, 'field_text'],
|
||
'beep-settings',
|
||
'beep_behavior',
|
||
['key' => 'posts_per_page', 'label' => 'Number of posts to load']
|
||
);
|
||
|
||
add_settings_field(
|
||
'public_posting',
|
||
'Allow Public Posting',
|
||
[__CLASS__, 'field_checkbox'],
|
||
'beep-settings',
|
||
'beep_behavior',
|
||
['key' => 'public_posting', 'label' => 'Allow posting without logging in']
|
||
);
|
||
|
||
// === Section: Content Display ===
|
||
add_settings_section(
|
||
'beep_content',
|
||
'Content Display',
|
||
[__CLASS__, 'section_content_description'],
|
||
'beep-settings'
|
||
);
|
||
|
||
add_settings_field(
|
||
'show_avatar',
|
||
'Show Avatars',
|
||
[__CLASS__, 'field_checkbox'],
|
||
'beep-settings',
|
||
'beep_content',
|
||
['key' => 'show_avatar', 'label' => 'Display user profile pictures']
|
||
);
|
||
|
||
add_settings_field(
|
||
'show_stats',
|
||
'Show Statistics',
|
||
[__CLASS__, 'field_checkbox'],
|
||
'beep-settings',
|
||
'beep_content',
|
||
['key' => 'show_stats', 'label' => 'Display reply/like counts']
|
||
);
|
||
|
||
add_settings_field(
|
||
'show_timestamps',
|
||
'Show Timestamps',
|
||
[__CLASS__, 'field_checkbox'],
|
||
'beep-settings',
|
||
'beep_content',
|
||
['key' => 'show_timestamps', 'label' => 'Display relative time']
|
||
);
|
||
|
||
add_settings_field(
|
||
'show_public_notice',
|
||
'Show Public Notice',
|
||
[__CLASS__, 'field_checkbox'],
|
||
'beep-settings',
|
||
'beep_content',
|
||
['key' => 'show_public_notice', 'label' => 'Show "Sign in to post" notice']
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Add settings page to admin menu
|
||
*/
|
||
public static function add_settings_page() {
|
||
add_options_page(
|
||
'Beep Settings',
|
||
'Beep',
|
||
'manage_options',
|
||
'beep-settings',
|
||
[__CLASS__, 'render_settings_page']
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Add settings link on Plugins page
|
||
*/
|
||
public static function add_settings_link($links) {
|
||
$settings_link = '<a href="options-general.php?page=beep-settings">' . __('Settings') . '</a>';
|
||
array_unshift($links, $settings_link);
|
||
return $links;
|
||
}
|
||
|
||
/**
|
||
* Sanitize settings on save
|
||
*/
|
||
public static function sanitize_settings($input) {
|
||
$sanitized = [];
|
||
|
||
foreach (self::$defaults as $key => $default) {
|
||
if (is_bool($default)) {
|
||
$sanitized[$key] = !empty($input[$key]) ? '1' : '0';
|
||
} elseif (is_int($default)) {
|
||
$sanitized[$key] = isset($input[$key]) ? intval($input[$key]) : $default;
|
||
} else {
|
||
$sanitized[$key] = isset($input[$key]) ? sanitize_text_field($input[$key]) : $default;
|
||
}
|
||
}
|
||
|
||
return $sanitized;
|
||
}
|
||
|
||
/**
|
||
* Render the settings page
|
||
*/
|
||
public static function render_settings_page() {
|
||
if (!current_user_can('manage_options')) {
|
||
return;
|
||
}
|
||
?>
|
||
<div class="wrap beep-settings-wrap">
|
||
<h1>Beep Settings</h1>
|
||
|
||
<div class="beep-settings-header">
|
||
<p>Customize the appearance and behavior of your Beep feed. Changes take effect immediately.</p>
|
||
<p>
|
||
<strong>Quick Presets:</strong>
|
||
<button type="button" class="button beep-preset-twitter" data-preset="twitter">Twitter Dark</button>
|
||
<button type="button" class="button beep-preset-light" data-preset="light">Twitter Light</button>
|
||
<button type="button" class="button beep-preset-minimal" data-preset="minimal">Minimal</button>
|
||
</p>
|
||
</div>
|
||
|
||
<form method="post" action="options.php">
|
||
<?php
|
||
settings_fields(self::OPTION_KEY);
|
||
do_settings_sections('beep-settings');
|
||
submit_button('Save Settings');
|
||
?>
|
||
</form>
|
||
|
||
<hr>
|
||
|
||
<div class="beep-settings-footer">
|
||
<h2>Need Help?</h2>
|
||
<p>Use your browser's Inspector tool to find colors you want to match. Click any element and look at the computed styles.</p>
|
||
<p><strong>Want to go fully Twitter/X dark mode?</strong> Use these values:</p>
|
||
<ul style="list-style: disc; margin-left: 20px;">
|
||
<li>Main Background: <code>#15202B</code></li>
|
||
<li>Card Background: <code>#192734</code></li>
|
||
<li>Primary Text: <code>#E7E9EA</code></li>
|
||
<li>Secondary Text: <code>#71767B</code></li>
|
||
<li>Link Color: <code>#1D9BF0</code></li>
|
||
<li>Like Heart: <code>#F91880</code></li>
|
||
<li>Border Color: <code>#2F3336</code></li>
|
||
<li>Card Radius: <code>0px</code> (for sharp Twitter-like edges)</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
<style>
|
||
.beep-settings-wrap { max-width: 800px; }
|
||
.beep-settings-wrap .form-table th { font-weight: 600; }
|
||
.beep-settings-wrap input[type="text"] { width: 200px; }
|
||
.beep-settings-wrap .beep-color-preview { display: inline-block; width: 20px; height: 20px; border: 1px solid #ccc; vertical-align: middle; margin-right: 8px; border-radius: 3px; }
|
||
.beep-settings-header { background: #f0f0f1; padding: 15px 20px; border-radius: 8px; margin-bottom: 20px; }
|
||
.beep-settings-footer { background: #f0f0f1; padding: 15px 20px; border-radius: 8px; margin-top: 30px; }
|
||
.beep-settings-footer code { background: #fff; padding: 2px 6px; border-radius: 3px; }
|
||
</style>
|
||
|
||
<script>
|
||
jQuery(document).ready(function($) {
|
||
// Color picker enhancement
|
||
$('.beep-color-picker').each(function() {
|
||
var $input = $(this);
|
||
var $preview = $input.siblings('.beep-color-preview');
|
||
|
||
$input.on('input', function() {
|
||
$preview.css('background-color', $(this).val());
|
||
});
|
||
|
||
// Initialize preview
|
||
$preview.css('background-color', $input.val());
|
||
});
|
||
|
||
// Preset buttons
|
||
$('.beep-preset-twitter').on('click', function() {
|
||
var preset = <?php echo json_encode([
|
||
'bg_main' => '#15202B', 'bg_card' => '#192734', 'bg_card_hover' => '#1C2732', 'bg_input' => '#253341',
|
||
'text_primary' => '#E7E9EA', 'text_secondary' => '#71767B', 'text_link' => '#1D9BF0',
|
||
'color_like' => '#F91880', 'color_repost' => '#00BA7C', 'color_reply' => '#1D9BF0',
|
||
'border_color' => '#2F3336', 'radius_card' => '0px', 'radius_avatar' => '9999px',
|
||
]); ?>;
|
||
applyPreset(preset);
|
||
});
|
||
|
||
$('.beep-preset-light').on('click', function() {
|
||
var preset = <?php echo json_encode([
|
||
'bg_main' => '#FFFFFF', 'bg_card' => '#F7F9F9', 'bg_card_hover' => '#EFF3F4', 'bg_input' => '#F7F9F9',
|
||
'text_primary' => '#0F1419', 'text_secondary' => '#536471', 'text_link' => '#1D9BF0',
|
||
'color_like' => '#F91880', 'color_repost' => '#00BA7C', 'color_reply' => '#1D9BF0',
|
||
'border_color' => '#EFF3F4', 'radius_card' => '16px', 'radius_avatar' => '9999px',
|
||
]); ?>;
|
||
applyPreset(preset);
|
||
});
|
||
|
||
$('.beep-preset-minimal').on('click', function() {
|
||
var preset = <?php echo json_encode([
|
||
'bg_main' => '#1a1a1a', 'bg_card' => '#1a1a1a', 'bg_card_hover' => '#262626', 'bg_input' => '#262626',
|
||
'text_primary' => '#e0e0e0', 'text_secondary' => '#888888', 'text_link' => '#ffffff',
|
||
'color_like' => '#ff4444', 'color_repost' => '#44ff44', 'color_reply' => '#4444ff',
|
||
'border_color' => '#333333', 'radius_card' => '0px', 'radius_avatar' => '0px',
|
||
]); ?>;
|
||
applyPreset(preset);
|
||
});
|
||
|
||
function applyPreset(preset) {
|
||
Object.keys(preset).forEach(function(key) {
|
||
var $input = $('input[name="beep_settings[' + key + ']"]');
|
||
if ($input.length) {
|
||
$input.val(preset[key]).trigger('input');
|
||
}
|
||
});
|
||
}
|
||
|
||
// Media upload handling
|
||
$('.beep-media-upload-btn').on('click', function() {
|
||
var $btn = $(this);
|
||
var targetId = $btn.data('target');
|
||
var type = $btn.data('type') || 'icon';
|
||
var $input = $('#' + targetId);
|
||
var $wrapper = $btn.closest('[data-beep-upload-wrapper]');
|
||
var $previewArea = $wrapper.find('[data-beep-preview-area]');
|
||
|
||
var mediaFrame = wp.media({
|
||
title: 'Choose ' + (type === 'logo' ? 'Logo Image' : 'Icon Image'),
|
||
button: { text: 'Use as ' + (type === 'logo' ? 'Logo' : 'Icon') },
|
||
multiple: false,
|
||
library: { type: 'image' }
|
||
});
|
||
|
||
mediaFrame.on('select', function() {
|
||
var attachment = mediaFrame.state().get('selection').first().toJSON();
|
||
$input.val(attachment.url);
|
||
renderPreview(attachment.url, type, $previewArea);
|
||
});
|
||
|
||
mediaFrame.open();
|
||
});
|
||
|
||
// Live preview on URL paste/type
|
||
$('.beep-media-input').on('input', function() {
|
||
var $input = $(this);
|
||
var $wrapper = $input.closest('[data-beep-upload-wrapper]');
|
||
var $previewArea = $wrapper.find('[data-beep-preview-area]');
|
||
var type = $input.attr('name').includes('logo') ? 'logo' : 'icon';
|
||
var url = $input.val().trim();
|
||
|
||
if (url && (url.startsWith('http://') || url.startsWith('https://'))) {
|
||
renderPreview(url, type, $previewArea);
|
||
} else {
|
||
renderEmptyPreview($previewArea);
|
||
}
|
||
});
|
||
|
||
// Render preview helper
|
||
function renderPreview(url, type, $area) {
|
||
var recommended = type === 'logo'
|
||
? '300×80 px minimum'
|
||
: '32×32 px';
|
||
var displayW = type === 'logo' ? 280 : 80;
|
||
var displayH = type === 'logo' ? 80 : 80;
|
||
|
||
var $img = $('<img/>', {
|
||
src: url,
|
||
alt: 'Preview',
|
||
crossOrigin: 'anonymous',
|
||
css: {
|
||
maxWidth: displayW + 'px',
|
||
maxHeight: displayH + 'px',
|
||
border: '1px solid #ccc',
|
||
borderRadius: '6px',
|
||
background: '#fff',
|
||
display: 'block'
|
||
}
|
||
});
|
||
|
||
var $imgWrapper = $('<div/>', {
|
||
css: {
|
||
display: 'inline-block',
|
||
padding: '12px',
|
||
background: '#fff',
|
||
border: '1px solid #EFF3F4',
|
||
borderRadius: '8px'
|
||
}
|
||
}).append($img);
|
||
|
||
var $badge = $('<span/>', {
|
||
text: '⚖ Pending size check…',
|
||
css: { fontSize: '11px', color: '#71767B', fontFamily: 'monospace', display: 'block', marginTop: '6px' }
|
||
});
|
||
|
||
$img.on('load', function() {
|
||
var w = this.naturalWidth || this.width;
|
||
var h = this.naturalHeight || this.height;
|
||
var ok = type === 'logo' ? (w >= 150 && h >= 40) : (w >= 16 && h >= 16);
|
||
var label = type === 'logo' ? 'Logo' : 'Icon';
|
||
|
||
var $sizeInfo = $('<div/>', {
|
||
css: { marginTop: '8px', fontSize: '12px', fontFamily: 'monospace' }
|
||
});
|
||
|
||
$sizeInfo.append(
|
||
$('<span/>', {
|
||
text: label + ' size: ' + w + '×' + h + 'px',
|
||
css: { color: ok ? '#00BA7C' : '#F91880', fontWeight: '600' }
|
||
})
|
||
);
|
||
$sizeInfo.append(
|
||
$('<br><span/>', {
|
||
text: 'Recommended: ' + recommended,
|
||
css: { color: '#71767B' }
|
||
})
|
||
);
|
||
if (!ok) {
|
||
$sizeInfo.append(
|
||
$('<br><span/>', {
|
||
text: '⚠ May appear blurry — larger source recommended',
|
||
css: { color: '#F91880', fontWeight: '500' }
|
||
})
|
||
);
|
||
}
|
||
|
||
// Render the full preview area
|
||
$area.html('').append(
|
||
$('<div/>', { css: { display: 'flex', alignItems: 'flex-start', gap: '16px' } }).append(
|
||
$imgWrapper,
|
||
$('<div/>').append(
|
||
$('<div/>', {
|
||
text: '✓ Live preview',
|
||
css: { fontSize: '12px', fontWeight: '600', color: '#00BA7C', marginBottom: '4px' }
|
||
}),
|
||
$sizeInfo,
|
||
$('<div/>', {
|
||
text: 'In feed header (wordmark):',
|
||
css: { fontSize: '11px', color: '#71767B', marginTop: '8px', marginBottom: '4px', textTransform: 'uppercase', letterSpacing: '.5px' }
|
||
}),
|
||
$('<img/>', {
|
||
src: url,
|
||
alt: label + ' in header',
|
||
css: {
|
||
maxWidth: '200px',
|
||
maxHeight: '48px',
|
||
border: '1px solid #EFF3F4',
|
||
borderRadius: '4px',
|
||
display: 'block'
|
||
}
|
||
}),
|
||
$('<div/>', {
|
||
text: 'In post (badge):',
|
||
css: { fontSize: '11px', color: '#71767B', marginTop: '8px', marginBottom: '4px', textTransform: 'uppercase', letterSpacing: '.5px' }
|
||
}),
|
||
$('<img/>', {
|
||
src: url,
|
||
alt: label + ' badge',
|
||
css: {
|
||
maxWidth: '32px',
|
||
maxHeight: '32px',
|
||
border: '1px solid #EFF3F4',
|
||
borderRadius: '4px',
|
||
display: 'block'
|
||
}
|
||
}),
|
||
$('<img/>', {
|
||
src: url,
|
||
alt: label + ' badge (reply)',
|
||
css: {
|
||
maxWidth: '24px',
|
||
maxHeight: '24px',
|
||
border: '1px solid #EFF3F4',
|
||
borderRadius: '4px',
|
||
display: 'block',
|
||
marginTop: '4px'
|
||
}
|
||
})
|
||
)
|
||
)
|
||
);
|
||
}).on('error', function() {
|
||
$area.html(
|
||
'<div style="color:#b32d2e;font-size:13px;padding:8px;">Could not load image — check the URL</div>'
|
||
);
|
||
});
|
||
}
|
||
|
||
function renderEmptyPreview($area) {
|
||
$area.html(
|
||
'<div class="beep-upload-empty" style="display:flex;align-items:center;gap:12px;padding:10px 14px;background:#F5F8FA;border:1px dashed #CBD0D4;border-radius:8px;color:#536471;font-size:13px;">' +
|
||
'<svg viewBox="0 0 24 24" width="20" height="20" fill="#536471"><path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/></svg>' +
|
||
'No image chosen yet — click "Choose Image" or paste a URL above' +
|
||
'</div>'
|
||
);
|
||
}
|
||
|
||
// Initialize previews on page load
|
||
$('.beep-media-input').each(function() {
|
||
var $input = $(this);
|
||
var $wrapper = $input.closest('[data-beep-upload-wrapper]');
|
||
var $previewArea = $wrapper.find('[data-beep-preview-area]');
|
||
var type = $input.attr('name').includes('logo') ? 'logo' : 'icon';
|
||
var url = $input.val().trim();
|
||
|
||
if (url && (url.startsWith('http://') || url.startsWith('https://'))) {
|
||
renderPreview(url, type, $previewArea);
|
||
}
|
||
});
|
||
|
||
// Remove media button
|
||
$(document).on('click', '.beep-media-remove', function() {
|
||
var targetId = $(this).data('target');
|
||
var previewId = $(this).data('preview');
|
||
$('#' + targetId).val('');
|
||
var $wrapper = $('#' + targetId).closest('[data-beep-upload-wrapper]');
|
||
var $previewArea = $wrapper.find('[data-beep-preview-area]');
|
||
renderEmptyPreview($previewArea);
|
||
// Remove the "Remove" button itself
|
||
$(this).remove();
|
||
});
|
||
});
|
||
</script>
|
||
<?php
|
||
}
|
||
|
||
/**
|
||
* Inject CSS variables into <head>
|
||
*/
|
||
public static function inject_css_variables() {
|
||
$settings = self::get_settings();
|
||
?>
|
||
<style id="beep-custom-css-vars">
|
||
:root {
|
||
--beep-bg: <?php echo esc_attr($settings['bg_main']); ?>;
|
||
--beep-card-bg: <?php echo esc_attr($settings['bg_card']); ?>;
|
||
--beep-card-hover: <?php echo esc_attr($settings['bg_card_hover']); ?>;
|
||
--beep-input-bg: <?php echo esc_attr($settings['bg_input']); ?>;
|
||
--beep-text: <?php echo esc_attr($settings['text_primary']); ?>;
|
||
--beep-muted: <?php echo esc_attr($settings['text_secondary']); ?>;
|
||
--beep-accent: <?php echo esc_attr($settings['text_link']); ?>;
|
||
--beep-border: <?php echo esc_attr($settings['border_color']); ?>;
|
||
--beep-border-hover: <?php echo esc_attr($settings['border_hover']); ?>;
|
||
--beep-like: <?php echo esc_attr($settings['color_like']); ?>;
|
||
--beep-repost: <?php echo esc_attr($settings['color_repost']); ?>;
|
||
--beep-reply: <?php echo esc_attr($settings['color_reply']); ?>;
|
||
--beep-quote: <?php echo esc_attr($settings['color_quote']); ?>;
|
||
--beep-poll: <?php echo esc_attr($settings['color_poll']); ?>;
|
||
--beep-btn-primary-bg: <?php echo esc_attr($settings['btn_primary_bg']); ?>;
|
||
--beep-btn-primary-text: <?php echo esc_attr($settings['btn_primary_text']); ?>;
|
||
--beep-btn-secondary-bg: <?php echo esc_attr($settings['btn_secondary_bg']); ?>;
|
||
--beep-btn-secondary-text: <?php echo esc_attr($settings['btn_secondary_text']); ?>;
|
||
--beep-avatar-ring: <?php echo esc_attr($settings['avatar_ring']); ?>;
|
||
--beep-feed-max-width: <?php echo esc_attr($settings['feed_max_width']); ?>;
|
||
--beep-feed-padding: <?php echo esc_attr($settings['feed_padding']); ?>;
|
||
--beep-post-padding: <?php echo esc_attr($settings['post_padding']); ?>;
|
||
--beep-avatar-size: <?php echo esc_attr($settings['avatar_size']); ?>;
|
||
--beep-gap: <?php echo esc_attr($settings['gap_between_posts']); ?>;
|
||
--beep-radius-card: <?php echo esc_attr($settings['radius_card']); ?>;
|
||
--beep-radius-avatar: <?php echo esc_attr($settings['radius_avatar']); ?>;
|
||
--beep-radius-button: <?php echo esc_attr($settings['radius_button']); ?>;
|
||
--beep-radius-input: <?php echo esc_attr($settings['radius_input']); ?>;
|
||
--beep-font-family: <?php echo esc_attr($settings['font_family']); ?>;
|
||
--beep-font-size: <?php echo esc_attr($settings['font_size_base']); ?>;
|
||
--beep-font-size-small: <?php echo esc_attr($settings['font_size_small']); ?>;
|
||
--beep-font-size-large: <?php echo esc_attr($settings['font_size_large']); ?>;
|
||
--beep-animation: <?php echo esc_attr($settings['animation_speed']); ?>ms ease;
|
||
}
|
||
|
||
/* Override page-level backgrounds (fixes Elementor purple gradient) */
|
||
body, .elementor, .elementor-section-wrap, .elementor-page, .elementor-editor-active body {
|
||
background: var(--beep-bg) !important;
|
||
}
|
||
</style>
|
||
<?php
|
||
}
|
||
|
||
// === Section Descriptions ===
|
||
public static function section_colors_bg_description() {
|
||
echo '<p>Background colors for the feed and cards.</p>';
|
||
}
|
||
|
||
public static function section_colors_text_description() {
|
||
echo '<p>Text colors throughout the feed.</p>';
|
||
}
|
||
|
||
public static function section_colors_interactive_description() {
|
||
echo '<p>Colors for interactive elements like buttons and icons.</p>';
|
||
}
|
||
|
||
public static function section_layout_description() {
|
||
echo '<p>Spacing and layout dimensions.</p>';
|
||
}
|
||
|
||
public static function section_radius_description() {
|
||
echo '<p>Border radius for rounded corners. Use <code>0px</code> for sharp edges (Twitter style), <code>16px</code> for rounded cards.</p>';
|
||
}
|
||
|
||
public static function section_typography_description() {
|
||
echo '<p>Font settings.</p>';
|
||
}
|
||
|
||
public static function section_branding_description() {
|
||
echo '<p>Logo, icon, and site name settings.</p>';
|
||
}
|
||
|
||
public static function section_features_description() {
|
||
echo '<p>Toggle features on or off.</p>';
|
||
}
|
||
|
||
public static function section_behavior_description() {
|
||
echo '<p>How the feed behaves.</p>';
|
||
}
|
||
|
||
public static function section_content_description() {
|
||
echo '<p>What to show/hide in the feed.</p>';
|
||
}
|
||
|
||
// === Field Renderers ===
|
||
public static function field_color_picker($args) {
|
||
$key = $args['key'];
|
||
$label = $args['label'];
|
||
$value = self::get($key);
|
||
?>
|
||
<span class="beep-color-preview" style="background-color: <?php echo esc_attr($value); ?>"></span>
|
||
<input type="text"
|
||
name="beep_settings[<?php echo esc_attr($key); ?>]"
|
||
id="beep_settings_<?php echo esc_attr($key); ?>"
|
||
value="<?php echo esc_attr($value); ?>"
|
||
class="beep-color-picker regular-text"
|
||
placeholder="#000000"
|
||
data-default-color="<?php echo esc_attr(self::$defaults[$key]); ?>">
|
||
<p class="description"><?php echo esc_html($label); ?></p>
|
||
<?php
|
||
}
|
||
|
||
public static function field_text($args) {
|
||
$key = $args['key'];
|
||
$label = $args['label'];
|
||
$value = self::get($key);
|
||
?>
|
||
<input type="text"
|
||
name="beep_settings[<?php echo esc_attr($key); ?>]"
|
||
id="beep_settings_<?php echo esc_attr($key); ?>"
|
||
value="<?php echo esc_attr($value); ?>"
|
||
class="regular-text">
|
||
<p class="description"><?php echo esc_html($label); ?></p>
|
||
<?php
|
||
}
|
||
|
||
public static function field_checkbox($args) {
|
||
$key = $args['key'];
|
||
$label = $args['label'];
|
||
$value = self::get($key);
|
||
?>
|
||
<label for="beep_settings_<?php echo esc_attr($key); ?>">
|
||
<input type="checkbox"
|
||
name="beep_settings[<?php echo esc_attr($key); ?>]"
|
||
id="beep_settings_<?php echo esc_attr($key); ?>"
|
||
value="1"
|
||
<?php checked($value, '1'); ?>>
|
||
<?php echo esc_html($label); ?>
|
||
</label>
|
||
<?php
|
||
}
|
||
|
||
public static function field_media_upload($args) {
|
||
$key = $args['key'];
|
||
$label = $args['label'];
|
||
$value = self::get($key);
|
||
$is_logo = ($key === 'logo_url');
|
||
$spec_label = $is_logo ? 'Logo (Wordmark)' : 'Icon (Badge)';
|
||
$recommended = $is_logo ? '300×80 px minimum, transparent PNG recommended' : '32×32 px (shown at 32px and 24px)';
|
||
?>
|
||
|
||
<!-- Spec callout -->
|
||
<div class="beep-upload-spec" style="background:#f0f6ff;border-left:3px solid #1D9BF0;padding:8px 12px;margin-bottom:10px;border-radius:4px;font-size:13px;color:#0F1419;">
|
||
<strong style="font-size:12px;text-transform:uppercase;letter-spacing:.5px;color:#536471;">Recommended for <?php echo esc_html($spec_label); ?>:</strong><br>
|
||
<span style="font-family:monospace;font-size:12px;"><?php echo esc_html($recommended); ?></span>
|
||
<?php if ($is_logo) : ?>
|
||
<br><span style="color:#71767B;">Transparent PNG/SVG — the header has a blurred backdrop</span>
|
||
<?php else : ?>
|
||
<br><span style="color:#71767B;">Will be displayed at 32×32px in posts, 24×24px in replies</span>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<div class="beep-media-upload-wrapper" data-beep-upload-wrapper style="display:flex;flex-direction:column;gap:12px;">
|
||
<!-- Upload row -->
|
||
<div style="display:flex;align-items:center;gap:10px;">
|
||
<input type="text"
|
||
name="beep_settings[<?php echo esc_attr($key); ?>]"
|
||
id="beep_settings_<?php echo esc_attr($key); ?>"
|
||
value="<?php echo esc_attr($value); ?>"
|
||
class="regular-text beep-media-input"
|
||
placeholder="https://... or leave empty for default"
|
||
style="flex:1;">
|
||
<button type="button" class="button beep-media-upload-btn"
|
||
data-target="beep_settings_<?php echo esc_attr($key); ?>"
|
||
data-type="<?php echo esc_attr($is_logo ? 'logo' : 'icon'); ?>">
|
||
Choose Image
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Preview area -->
|
||
<div class="beep-upload-preview-area" data-beep-preview-area="<?php echo esc_attr($key); ?>">
|
||
<?php if (!empty($value)) : ?>
|
||
<!-- Live preview (populated by JS on load + on change) -->
|
||
<?php else : ?>
|
||
<div class="beep-upload-empty" style="display:flex;align-items:center;gap:12px;padding:10px 14px;background:#F5F8FA;border:1px dashed #CBD0D4;border-radius:8px;color:#536471;font-size:13px;">
|
||
<svg viewBox="0 0 24 24" width="20" height="20" fill="#536471"><path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/></svg>
|
||
No image chosen yet — click "Choose Image" or paste a URL above
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<?php if (!empty($value)) : ?>
|
||
<button type="button" class="button-link beep-media-remove"
|
||
data-target="beep_settings_<?php echo esc_attr($key); ?>"
|
||
data-preview="beep_upload_preview_<?php echo esc_attr($key); ?>"
|
||
style="color:#b32d2e;text-decoration:none;font-size:13px;align-self:flex-start;">
|
||
Remove image
|
||
</button>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<p class="description" style="margin-top:0;"><?php echo esc_html($label); ?></p>
|
||
<?php
|
||
}
|
||
}
|
||
|
||
// Initialize settings
|
||
Beep_Settings::init();
|