v1.9.0: reply composer media support (images, GIF, voice, quotes, polls on replies)

This commit is contained in:
Krystie
2026-09-07 16:15:48 -07:00
parent 64344bfef6
commit 6eb128b780
8 changed files with 859 additions and 105 deletions
+191 -62
View File
@@ -146,62 +146,11 @@ function beep_render_composer() {
placeholder="What's happening?"
maxlength="<?php echo (int) BEEP_CHAR_LIMIT; ?>"
rows="1"></textarea>
<div class="beep-compose-media-preview" data-beep-media-preview hidden></div>
<div class="beep-compose-voice-preview" data-beep-voice-preview hidden>
<div class="beep-voice-preview-card">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm-1-9c0-.55.45-1 1-1s1 .45 1 1v6c0 .55-.45 1-1 1s-1-.45-1-1V5zm6 6c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08C16.39 17.43 19 14.53 19 11h-2z"/></svg>
<span>Voice note</span>
<button class="beep-voice-remove" data-beep-voice-remove type="button" title="Remove">&times;</button>
</div>
</div>
<div class="beep-compose-gif" data-beep-gif-preview hidden></div>
<div class="beep-compose-quote" data-beep-compose-quote hidden>
<div class="beep-quote-input-row">
<input type="text" class="beep-quote-lookup" placeholder="Paste beep URL or ID..." data-beep-quote-lookup>
<button type="button" class="beep-quote-lookup-btn" data-beep-quote-lookup-btn>Add</button>
</div>
<div class="beep-quote-preview" data-beep-quote-preview hidden></div>
<button type="button" class="beep-quote-remove" data-beep-quote-remove hidden>Remove quote</button>
</div>
<div class="beep-compose-poll" data-beep-compose-poll hidden>
<div class="beep-poll-input-row">
<input type="text" class="beep-poll-question" data-beep-poll-question placeholder="Ask a question...">
</div>
<div class="beep-poll-options" data-beep-poll-options>
<div class="beep-poll-option-row">
<input type="text" class="beep-poll-option" data-beep-poll-option placeholder="Option 1" maxlength="100">
</div>
<div class="beep-poll-option-row">
<input type="text" class="beep-poll-option" data-beep-poll-option placeholder="Option 2" maxlength="100">
</div>
</div>
<div class="beep-poll-actions">
<button type="button" class="beep-poll-add-option" data-beep-poll-add-option>+ Add option</button>
<button type="button" class="beep-poll-remove-option" data-beep-poll-remove-option hidden>&minus; Remove</button>
<button type="button" class="beep-poll-remove" data-beep-poll-remove>Remove poll</button>
</div>
</div>
<?php echo beep_render_media_previews('compose'); ?>
<?php echo beep_render_quote_section('compose'); ?>
<?php echo beep_render_poll_section('compose'); ?>
<div class="beep-compose-footer">
<div class="beep-compose-tools">
<label class="beep-compose-tool beep-media-btn" title="Add up to 4 images">
<input type="file" accept="image/jpeg,image/png,image/gif,image/webp" multiple data-beep-media-input hidden>
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M3 5.5C3 4.119 4.119 3 5.5 3h13C19.881 3 21 4.119 21 5.5v13c0 1.381-1.119 2.5-2.5 2.5h-13C4.119 21 3 19.881 3 18.5v-13zM5.5 5c-.276 0-.5.224-.5.5v9.086l3-3 3 3 5-5 3 3V5.5c0-.276-.224-.5-.5-.5h-13zM19 15.414l-3-3-5 5-3-3-3 3V18.5c0 .276.224.5.5.5h13c.276 0 .5-.224.5-.5v-3.086zM9.75 7C8.784 7 8 7.784 8 8.75s.784 1.75 1.75 1.75 1.75-.784 1.75-1.75S10.716 7 9.75 7z"/></svg>
</label>
<span class="beep-media-count" data-beep-media-count hidden>0/4</span>
<button type="button" class="beep-compose-tool beep-gif-btn" data-beep-gif-btn title="Add GIF">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M3 5.5C3 4.119 4.119 3 5.5 3h13C19.881 3 21 4.119 21 5.5v13c0 1.381-1.119 2.5-2.5 2.5h-13C4.119 21 3 19.881 3 18.5v-13zM5.5 5c-.276 0-.5.224-.5.5v13c0 .276.224.5.5.5h13c.276 0 .5-.224.5-.5v-13c0-.276-.224-.5-.5-.5h-13zM18 10.711V9.25h-3.74v5.5h1.44v-1.719h1.7V11.57h-1.7v-.859H18zM11.79 9.25h1.44v5.5h-1.44v-5.5zm-3.07 1.375c.34 0 .77.172 1.02.43l1.03-.86c-.51-.601-1.28-.945-2.05-.945C7.19 9.25 6 10.453 6 12s1.19 2.75 2.72 2.75c.85 0 1.54-.344 2.05-.945v-2.149H8.38v1.032H9.4v.515c-.17.086-.42.172-.68.172-.76 0-1.36-.602-1.36-1.375 0-.688.6-1.375 1.36-1.375z"/></svg>
</button>
<label class="beep-compose-tool beep-voice-btn" title="Add voice note">
<input type="file" accept="audio/mpeg,audio/ogg,audio/wav,audio/webm,audio/mp4,audio/x-m4a" data-beep-voice-input hidden>
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm-1-9c0-.55.45-1 1-1s1 .45 1 1v6c0 .55-.45 1-1 1s-1-.45-1-1V5zm6 6c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08C16.39 17.43 19 14.53 19 11h-2z"/></svg>
</label>
<button type="button" class="beep-compose-tool beep-poll-btn" data-beep-poll-btn title="Add poll">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M6 5c-1.1 0-2 .895-2 2s.9 2 2 2 2-.895 2-2-.9-2-2-2zM2 7c0-2.209 1.79-4 4-4s4 1.791 4 4-1.79 4-4 4-4-1.791-4-4zm20 1H12V6h10v2zM6 15c-1.1 0-2 .895-2 2s.9 2 2 2 2-.895 2-2-.9-2-2-2zm-4 2c0-2.209 1.79-4 4-4s4 1.791 4 4-1.79 4-4 4-4-1.791-4-4zm20 1H12v-2h10v2z"/></svg>
</button>
<button type="button" class="beep-compose-tool beep-quote-composer-btn" data-beep-quote-composer-btn title="Quote a beep">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M14.23 2.854c.98-.977 2.56-.977 3.54 0l3.38 3.378c.97.977.97 2.559 0 3.536L9.91 21H3v-6.914L14.23 2.854zm2.12 1.414c-.19-.195-.51-.195-.7 0L5 14.914V19h4.09L19.73 8.354c.2-.196.2-.512 0-.708l-3.38-3.378z"/></svg>
</button>
</div>
<?php echo beep_render_compose_toolbar('compose'); ?>
<div class="beep-compose-submit-row">
<span class="beep-char-counter" data-beep-counter><?php echo (int) BEEP_CHAR_LIMIT; ?></span>
<button class="beep-button beep-post-button" data-beep-submit disabled>Beep</button>
@@ -213,6 +162,136 @@ function beep_render_composer() {
return ob_get_clean();
}
/**
* Render the media previews block (image grid + voice card + GIF card).
* Shared between compose and reply forms.
*
* @param string $scope 'compose' or 'reply'
*/
function beep_render_media_previews($scope = 'compose') {
$cls = $scope === 'reply' ? 'beep-reply' : 'beep-compose';
?>
<div class="<?php echo esc_attr($cls); ?>-media-preview" data-beep-media-preview hidden></div>
<div class="<?php echo esc_attr($cls); ?>-voice-preview" data-beep-voice-preview hidden>
<div class="beep-voice-preview-card">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm-1-9c0-.55.45-1 1-1s1 .45 1 1v6c0 .55-.45 1-1 1s-1-.45-1-1V5zm6 6c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08C16.39 17.43 19 14.53 19 11h-2z"/></svg>
<span>Voice note</span>
<button class="beep-voice-remove" data-beep-voice-remove type="button" title="Remove">&times;</button>
</div>
</div>
<div class="<?php echo esc_attr($cls); ?>-gif" data-beep-gif-preview hidden></div>
<?php
}
/**
* Render the quote-lookup section (input + preview).
* Shared between compose and reply forms.
*/
function beep_render_quote_section($scope = 'compose') {
$cls = $scope === 'reply' ? 'beep-reply' : 'beep-compose';
?>
<div class="<?php echo esc_attr($cls); ?>-quote" data-beep-compose-quote hidden>
<div class="beep-quote-input-row">
<input type="text" class="beep-quote-lookup" placeholder="Paste beep URL or ID..." data-beep-quote-lookup>
<button type="button" class="beep-quote-lookup-btn" data-beep-quote-lookup-btn>Add</button>
</div>
<div class="beep-quote-preview" data-beep-quote-preview hidden></div>
<button type="button" class="beep-quote-remove" data-beep-quote-remove hidden>Remove quote</button>
</div>
<?php
}
/**
* Render the poll-creation section.
* Shared between compose and reply forms.
*/
function beep_render_poll_section($scope = 'compose') {
$cls = $scope === 'reply' ? 'beep-reply' : 'beep-compose';
?>
<div class="<?php echo esc_attr($cls); ?>-poll" data-beep-compose-poll hidden>
<div class="beep-poll-input-row">
<input type="text" class="beep-poll-question" data-beep-poll-question placeholder="Ask a question...">
</div>
<div class="beep-poll-options" data-beep-poll-options>
<div class="beep-poll-option-row">
<input type="text" class="beep-poll-option" data-beep-poll-option placeholder="Option 1" maxlength="100">
</div>
<div class="beep-poll-option-row">
<input type="text" class="beep-poll-option" data-beep-poll-option placeholder="Option 2" maxlength="100">
</div>
</div>
<div class="beep-poll-actions">
<button type="button" class="beep-poll-add-option" data-beep-poll-add-option>+ Add option</button>
<button type="button" class="beep-poll-remove-option" data-beep-poll-remove-option hidden>&minus; Remove</button>
<button type="button" class="beep-poll-remove" data-beep-poll-remove>Remove poll</button>
</div>
</div>
<?php
}
/**
* Render the compose toolbar (image / GIF / voice / poll / quote buttons).
* Shared between compose and reply forms.
*/
function beep_render_compose_toolbar($scope = 'compose') {
$cls = $scope === 'reply' ? 'beep-reply' : 'beep-compose';
?>
<div class="<?php echo esc_attr($cls); ?>-tools">
<label class="<?php echo esc_attr($cls); ?>-tool <?php echo esc_attr($cls); ?>-media-btn" title="Add up to 4 images">
<input type="file" accept="image/jpeg,image/png,image/gif,image/webp" multiple data-beep-media-input hidden>
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M3 5.5C3 4.119 4.119 3 5.5 3h13C19.881 3 21 4.119 21 5.5v13c0 1.381-1.119 2.5-2.5 2.5h-13C4.119 21 3 19.881 3 18.5v-13zM5.5 5c-.276 0-.5.224-.5.5v9.086l3-3 3 3 5-5 3 3V5.5c0-.276-.224-.5-.5-.5h-13zM19 15.414l-3-3-5 5-3-3-3 3V18.5c0 .276.224.5.5.5h13c.276 0 .5-.224.5-.5v-3.086zM9.75 7C8.784 7 8 7.784 8 8.75s.784 1.75 1.75 1.75 1.75-.784 1.75-1.75S10.716 7 9.75 7z"/></svg>
</label>
<span class="beep-media-count" data-beep-media-count hidden>0/4</span>
<button type="button" class="<?php echo esc_attr($cls); ?>-tool <?php echo esc_attr($cls); ?>-gif-btn" data-beep-gif-btn title="Add GIF">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M3 5.5C3 4.119 4.119 3 5.5 3h13C19.881 3 21 4.119 21 5.5v13c0 1.381-1.119 2.5-2.5 2.5h-13C4.119 21 3 19.881 3 18.5v-13zM5.5 5c-.276 0-.5.224-.5.5v13c0 .276.224.5.5.5h13c.276 0 .5-.224.5-.5v-13c0-.276-.224-.5-.5-.5h-13zM18 10.711V9.25h-3.74v5.5h1.44v-1.719h1.7V11.57h-1.7v-.859H18zM11.79 9.25h1.44v5.5h-1.44v-5.5zm-3.07 1.375c.34 0 .77.172 1.02.43l1.03-.86c-.51-.601-1.28-.945-2.05-.945C7.19 9.25 6 10.453 6 12s1.19 2.75 2.72 2.75c.85 0 1.54-.344 2.05-.945v-2.149H8.38v1.032H9.4v.515c-.17.086-.42.172-.68.172-.76 0-1.36-.602-1.36-1.375 0-.688.6-1.375 1.36-1.375z"/></svg>
</button>
<label class="<?php echo esc_attr($cls); ?>-tool <?php echo esc_attr($cls); ?>-voice-btn" title="Add voice note">
<input type="file" accept="audio/mpeg,audio/ogg,audio/wav,audio/webm,audio/mp4,audio/x-m4a" data-beep-voice-input hidden>
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm-1-9c0-.55.45-1 1-1s1 .45 1 1v6c0 .55-.45 1-1 1s-1-.45-1-1V5zm6 6c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08C16.39 17.43 19 14.53 19 11h-2z"/></svg>
</label>
<button type="button" class="<?php echo esc_attr($cls); ?>-tool <?php echo esc_attr($cls); ?>-poll-btn" data-beep-poll-btn title="Add poll">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M6 5c-1.1 0-2 .895-2 2s.9 2 2 2 2-.895 2-2-.9-2-2-2zM2 7c0-2.209 1.79-4 4-4s4 1.791 4 4-1.79 4-4 4-4-1.791-4-4zm20 1H12V6h10v2zM6 15c-1.1 0-2 .895-2 2s.9 2 2 2 2-.895 2-2-.9-2-2-2zm-4 2c0-2.209 1.79-4 4-4s4 1.791 4 4-1.79 4-4 4-4-1.791-4-4zm20 1H12v-2h10v2z"/></svg>
</button>
<button type="button" class="<?php echo esc_attr($cls); ?>-tool <?php echo esc_attr($cls); ?>-quote-composer-btn" data-beep-quote-composer-btn title="Quote a beep">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M14.23 2.854c.98-.977 2.56-.977 3.54 0l3.38 3.378c.97.977.97 2.559 0 3.536L9.91 21H3v-6.914L14.23 2.854zm2.12 1.414c-.19-.195-.51-.195-.7 0L5 14.914V19h4.09L19.73 8.354c.2-.196.2-.512 0-.708l-3.38-3.378z"/></svg>
</button>
</div>
<?php
}
/**
* Render a reply poll (v1.9.0).
* Mirrors beep_render_poll() for posts.
*/
function beep_render_reply_poll($comment_id) {
$poll = Beep_Replies::get_reply_poll($comment_id);
if (!$poll) return '';
$total = (int) $poll['total_votes'];
$opts = $poll['options'];
$votes = $poll['votes'];
$has_ended = $poll['ends_at'] && strtotime($poll['ends_at']) < time();
?>
<div class="beep-poll-container" data-beep-reply-poll data-beep-reply-poll-id="<?php echo (int) $poll['id']; ?>">
<div class="beep-poll-question"><?php echo esc_html($poll['question']); ?></div>
<div class="beep-poll-options">
<?php foreach ($opts as $i => $opt) :
$pct = $total > 0 ? round(((int) ($votes[$i] ?? 0)) * 100 / $total) : 0;
$has_voted = false; // kept simple for v1.9.0; full voting comes in v1.9.1
?>
<div class="beep-poll-result<?php echo $has_voted ? ' is-voted' : ''; ?>">
<div class="beep-poll-result-bar" style="width:<?php echo (int) $pct; ?>%"></div>
<span class="beep-poll-result-text"><?php echo esc_html($opt); ?></span>
<span class="beep-poll-result-percent"><?php echo (int) $pct; ?>%</span>
</div>
<?php endforeach; ?>
</div>
<div class="beep-poll-meta">
<span class="beep-poll-votes"><?php echo $total; ?> <?php echo $total === 1 ? 'vote' : 'votes'; ?></span>
</div>
</div>
<?php
}
/**
* Render a single beep post.
*
@@ -429,10 +508,13 @@ function beep_render_poll($post_id) {
* @param int $parent The parent comment ID (0 for top-level reply).
*/
function beep_render_reply_form($post_id, $parent) {
if ( ! is_user_logged_in() || beep_user_is_banned( get_current_user_id() ) ) {
return '';
}
$hidden = $parent ? 'hidden' : '';
ob_start();
?>
<div class="beep-reply-form" data-beep-reply-form data-beep-parent="<?php echo (int) $parent; ?>" <?php echo $hidden; ?>>
<div class="beep-reply-form beep-reply-form-compact" data-beep-reply-form data-beep-parent="<?php echo (int) $parent; ?>" <?php echo $hidden; ?>>
<?php echo beep_avatar_img(get_current_user_id(), 'beep-avatar-sm'); ?>
<div class="beep-reply-form-body">
<textarea
@@ -440,13 +522,19 @@ function beep_render_reply_form($post_id, $parent) {
placeholder="Post your reply"
maxlength="<?php echo (int) BEEP_CHAR_LIMIT; ?>"
rows="1"></textarea>
<?php echo beep_render_media_previews('reply'); ?>
<?php echo beep_render_quote_section('reply'); ?>
<?php echo beep_render_poll_section('reply'); ?>
<div class="beep-reply-footer">
<span class="beep-char-counter"><?php echo (int) BEEP_CHAR_LIMIT; ?></span>
<button class="beep-button beep-reply-submit"
data-beep-reply-submit="<?php echo (int) $post_id; ?>"
data-beep-reply-parent="<?php echo (int) $parent; ?>"
type="button"
disabled>Reply</button>
<?php echo beep_render_compose_toolbar('reply'); ?>
<div class="beep-reply-submit-row">
<span class="beep-char-counter"><?php echo (int) BEEP_CHAR_LIMIT; ?></span>
<button class="beep-button beep-reply-submit"
data-beep-reply-submit="<?php echo (int) $post_id; ?>"
data-beep-reply-parent="<?php echo (int) $parent; ?>"
type="button"
disabled>Reply</button>
</div>
</div>
</div>
</div>
@@ -492,6 +580,13 @@ function beep_render_reply($comment, $all_comments = [], $depth = 0, $post_id =
$like_count = Beep_Likes::get_count($comment->comment_ID, 'reply');
$user_liked = Beep_Likes::user_has_liked($comment->comment_ID, 'reply');
// Reply media (v1.9.0)
$reply_images = Beep_Replies::get_reply_images($comment->comment_ID);
$reply_voice = Beep_Replies::get_reply_voice_url($comment->comment_ID);
$reply_gif = Beep_Replies::get_reply_gif_url($comment->comment_ID);
$reply_quote_id = Beep_Replies::get_reply_quoted_post_id($comment->comment_ID);
$reply_quote = $reply_quote_id ? get_post($reply_quote_id) : null;
$depth_class = 'beep-depth-' . min((int) $depth, 2);
ob_start();
@@ -517,6 +612,40 @@ function beep_render_reply($comment, $all_comments = [], $depth = 0, $post_id =
</div>
<div class="beep-text"><?php echo $content; ?></div>
<?php if (!empty($reply_images)) : ?>
<div class="beep-media-attachments">
<?php echo Beep_Media::render_image_gallery($reply_images); ?>
</div>
<?php endif; ?>
<?php if ($reply_voice) : ?>
<div class="beep-media-attachments">
<div class="beep-voice-note">
<audio controls preload="none">
<source src="<?php echo esc_url($reply_voice); ?>">
Your browser does not support audio playback.
</audio>
<div class="beep-voice-label">
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm-1-9c0-.55.45-1 1-1s1 .45 1 1v6c0 .55-.45 1-1 1s-1-.45-1-1V5zm6 6c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08C16.39 17.43 19 14.53 19 11h-2z"/></svg>
Voice note
</div>
</div>
</div>
<?php endif; ?>
<?php if ($reply_quote && $reply_quote->post_type === 'beep') : ?>
<div class="beep-quoted-container">
<?php
$quoted_data = Beep_Quotes::get_quoted_post($reply_quote->ID);
if ($quoted_data) echo Beep_Quotes::render_quoted_post($reply_quote->ID);
?>
</div>
<?php endif; ?>
<?php if ($reply_gif) : ?>
<div class="beep-gif-embed">
<img src="<?php echo esc_url($reply_gif); ?>" alt="GIF" loading="lazy">
</div>
<?php endif; ?>
<?php echo beep_render_reply_poll($comment->comment_ID); ?>
<?php if ($can_interact) : ?>
<div class="beep-actions beep-actions-reply">
<button class="beep-action beep-action-reply"