Beep v1.5.1: Elementor widget, logo/icon settings with live preview, CSS isolation, JS fixes

This commit is contained in:
Krystie
2026-05-29 21:45:41 -07:00
parent 97ca5e1714
commit 94f063f09a
10 changed files with 623 additions and 211 deletions
+7 -1
View File
@@ -20,7 +20,13 @@ class Beep_Plugin {
update_option('beep_giphy_key', 'xuxYxuIvzPqw5tPB4laa7sQO8dFtSlNS');
}
// Admin settings
// Elementor widget
add_action('elementor/widgets/register', function ($widgets_manager) {
require_once BEEP_DIR . 'includes/class-elementor-widget.php';
$widgets_manager->register(new \Elementor\Beep_Elementor_Widget());
});
// Admin settings (Giphy API key only — full settings via Beep_Settings)
add_action('admin_menu', [__CLASS__, 'add_admin_menu']);
add_action('admin_init', [__CLASS__, 'register_settings']);
}
+127
View File
@@ -0,0 +1,127 @@
<?php
/**
* Beep Elementor Widget
* Drag-and-drop Elementor widget for the Beep feed.
*/
if (!defined('ABSPATH')) {
exit;
}
if (did_action('elementor/core')) {
return;
}
class Beep_Elementor_Widget extends \Elementor\Widget_Base {
public function get_name() {
return 'beep-feed';
}
public function get_title() {
return 'Beep Feed';
}
public function get_icon() {
return 'eicon-social-icons';
}
public function get_categories() {
return ['general'];
}
protected function register_controls() {
$this->start_controls_section(
'section_feed',
['label' => 'Feed Settings']
);
$this->add_control(
'header',
[
'label' => 'Header Title',
'type' => \Elementor\Controls_Manager::TEXT,
'default' => 'Beep',
'description' => 'Text shown in the feed header. Leave empty to show the wordmark logo.',
]
);
$this->add_control(
'show_wordmark',
[
'label' => 'Show Wordmark Logo',
'type' => \Elementor\Controls_Manager::SWITCHER,
'default' => 'yes',
]
);
$this->add_control(
'sort',
[
'label' => 'Default Sort',
'type' => \Elementor\Controls_Manager::SELECT,
'default' => 'latest',
'options' => [
'latest' => 'Latest First',
'top' => 'Top / Most Liked',
],
]
);
$this->add_control(
'limit',
[
'label' => 'Posts Per Page',
'type' => \Elementor\Controls_Manager::NUMBER,
'min' => 1,
'max' => 100,
'default' => 20,
]
);
$this->end_controls_section();
$this->start_controls_section(
'section_style',
['label' => 'Appearance']
);
$this->add_group_control(
\Elementor\Group_Control_Typography::get_type(),
[
'name' => 'feed_typography',
'label' => 'Feed Typography',
'selector' => '{{WRAPPER}} .beep-feed',
]
);
$this->add_control(
'card_radius',
[
'label' => 'Card Border Radius',
'type' => \Elementor\Controls_Manager::SLIDER,
'size_units' => ['px'],
'range' => ['px' => ['min' => 0, 'max' => 32]],
'default' => ['unit' => 'px', 'size' => 0],
'selectors' => [
'{{WRAPPER}} .beep-post' => 'border-radius: {{SIZE}}{{UNIT}};',
],
]
);
$this->end_controls_section();
}
protected function render() {
$settings = $this->get_settings_for_display();
$atts = [
'header' => $settings['header'] ?: 'Beep',
'show_wordmark' => $settings['show_wordmark'] ? '1' : '0',
'sort' => $settings['sort'],
'limit' => (int) $settings['limit'],
];
echo Beep_Shortcode::render_beep_feed($atts);
}
}
+290 -13
View File
@@ -54,6 +54,7 @@ class Beep_Settings {
// Branding
'logo_url' => '', // Custom logo URL
'icon_url' => '', // Custom icon URL (favicon)
'site_name' => 'Beep', // Or 'X' or 'Twitter'
'tagline' => 'Messages from',
@@ -361,14 +362,32 @@ class Beep_Settings {
['key' => 'font_size_base', 'label' => 'e.g. 15px']
);
// === Section: Branding ===
// === Section: Branding & Media ===
add_settings_section(
'beep_branding',
'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',
@@ -387,15 +406,6 @@ class Beep_Settings {
['key' => 'tagline', 'label' => 'Text above logo, e.g. "Messages from"']
);
add_settings_field(
'logo_url',
'Logo Image URL',
[__CLASS__, 'field_text'],
'beep-settings',
'beep_branding',
['key' => 'logo_url', 'label' => 'Leave empty to use text. Upload via Media Library.']
);
// === Section: Features ===
add_settings_section(
'beep_features',
@@ -691,6 +701,210 @@ class Beep_Settings {
}
});
}
// 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
@@ -773,7 +987,7 @@ class Beep_Settings {
}
public static function section_branding_description() {
echo '<p>Logo and site name settings.</p>';
echo '<p>Logo, icon, and site name settings.</p>';
}
public static function section_features_description() {
@@ -835,7 +1049,70 @@ class Beep_Settings {
</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();
+4 -2
View File
@@ -89,6 +89,7 @@ class Beep_Shortcode {
ob_start();
?>
<div class="beep-feed" data-beep-feed>
<div class="beep-embed">
<div class="beep-feed-header">
<?php if (!empty($atts['show_wordmark'])): ?>
<?php echo beep_logo_svg(); ?>
@@ -132,8 +133,9 @@ class Beep_Shortcode {
echo '<div class="beep-empty">No beeps yet.</div>';
}
?>
</div>
</div>
</div><!-- .beep-list -->
</div><!-- .beep-embed -->
</div><!-- .beep-feed -->
<?php
return ob_get_clean() . beep_render_thread_modal() . beep_render_gif_modal();
}
+4 -3
View File
@@ -7,7 +7,8 @@ if (!defined('ABSPATH')) {
* Wordmark (bird + "Beep!") shown at the top of the feed.
*/
function beep_logo_svg() {
$src = esc_url(BEEP_URL . 'assets/beep-wordmark.png');
$logo_url = Beep_Settings::get('logo_url');
$src = esc_url($logo_url ?: BEEP_URL . 'assets/beep-wordmark.png');
return '<img class="beep-logo" src="' . $src . '" alt="Beep">';
}
@@ -152,7 +153,7 @@ function beep_render_post($post) {
datetime="<?php echo esc_attr($time_iso); ?>"
title="<?php echo esc_attr($time_full); ?>"><?php echo esc_html($time); ?></time>
<span class="beep-spacer"></span>
<img class="beep-post-badge" src="<?php echo esc_url(BEEP_URL . 'assets/beep-icon.png'); ?>" alt="" width="32" height="32">
<img class="beep-post-badge" src="<?php echo esc_url(Beep_Settings::get('icon_url') ?: BEEP_URL . 'assets/beep-icon.png'); ?>" alt="" style="--beep-badge-size:32px;width:var(--beep-badge-size);height:var(--beep-badge-size);">
</div>
<div class="beep-text"><?php echo $content; ?></div>
<?php if (!empty($beep_images)) : ?>
@@ -374,7 +375,7 @@ function beep_render_reply($comment, $all_comments = [], $depth = 0, $post_id =
<span class="beep-dot">·</span>
<time class="beep-time" datetime="<?php echo esc_attr($time_iso); ?>"><?php echo esc_html($time); ?></time>
<span class="beep-spacer"></span>
<img class="beep-post-badge beep-post-badge-sm" src="<?php echo esc_url(BEEP_URL . 'assets/beep-icon.png'); ?>" alt="" width="24" height="24">
<img class="beep-post-badge beep-post-badge-sm" src="<?php echo esc_url(Beep_Settings::get('icon_url') ?: BEEP_URL . 'assets/beep-icon.png'); ?>" alt="" style="--beep-badge-size:24px;width:var(--beep-badge-size);height:var(--beep-badge-size);">
</div>
<div class="beep-text"><?php echo $content; ?></div>