[grade=B] v1.9.1: YouTube links auto-render as video cards in beeps, replies, and quotes (click-to-play, nocookie embed)

This commit is contained in:
Krystie
2026-09-07 16:21:23 -07:00
parent 6eb128b780
commit 2b65194cdb
6 changed files with 124 additions and 4 deletions
+60
View File
@@ -736,6 +736,66 @@
position: relative; position: relative;
} }
/* YouTube card (v1.9.1) */
.beep-embed .beep-yt-card {
position: relative;
margin-top: 12px;
border-radius: 16px;
overflow: hidden;
border: 1px solid var(--beep-border);
cursor: pointer;
background: #000;
aspect-ratio: 16 / 9;
display: flex;
align-items: center;
justify-content: center;
}
.beep-embed .beep-yt-thumb {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
top: 0;
left: 0;
margin: 0;
}
.beep-embed .beep-yt-play {
position: relative;
z-index: 1;
width: 68px;
height: 48px;
border-radius: 12px;
background: rgba(255, 0, 0, 0.85);
color: #fff;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.5);
transition: transform 0.15s ease, background 0.15s ease;
}
.beep-embed .beep-yt-card:hover .beep-yt-play,
.beep-embed .beep-yt-card:focus-visible .beep-yt-play {
background: #f00;
transform: scale(1.08);
}
.beep-embed .beep-yt-play svg {
fill: #fff;
margin-left: 4px;
}
.beep-embed .beep-yt-card iframe {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
border: 0;
}
.beep-embed .beep-gif-embed img { .beep-embed .beep-gif-embed img {
width: 100%; width: 100%;
height: auto; height: auto;
+27 -1
View File
@@ -428,10 +428,30 @@
return '<svg viewBox="0 0 24 24" width="64" height="64" fill="#4a90c4" aria-hidden="true"><path d="M17.2 1.4c-.8-.1-1.5.2-2 .6C14.3.9 13.2.7 12.1 1c-1.4.4-2.3 1.7-2.1 3.2-2.1-.2-4.2-.9-5.7-2.5-.3-.3-.8-.3-1 0-.6.8-.9 1.7-.9 2.6 0 1.2.5 2.2 1.3 2.9-.3-.1-.6-.1-.8-.3-.4-.2-.9 0-.8.5.3 1.6 1.3 2.8 2.7 3.4-.3 0-.5 0-.8-.1-.4-.1-.8.3-.5.7.8 1.4 2.2 2.3 3.8 2.5-1.3.8-2.8 1.2-4.4 1.2-.5 0-.8.5-.5.9 1.1 1.1 2.9 1.6 5.5 1.6 5.4 0 9.8-4.4 9.8-9.8v-.2c.9-.6 1.6-1.4 2.1-2.4.2-.4-.2-.8-.6-.6-.5.2-1 .3-1.5.4.5-.5.9-1.1 1-1.8.1-.4-.4-.7-.7-.5-.7.4-1.5.6-2.3.7-.8-.7-1.8-1.1-2.9-1.1zm-.8 1.6c.7-.1 1.4.2 1.9.7.2.2.5.3.8.2.3-.1.6-.1.9-.3-.2.3-.5.5-.8.6-.3.1-.4.5-.3.7.2.3.1.5 0 .8v.2c0 4.7-3.8 8.5-8.5 8.5-1.6 0-2.9-.2-3.8-.6 1.7-.3 3.2-1 4.3-2.2.3-.3.1-.8-.3-.8-1.4 0-2.7-.6-3.5-1.7.4 0 .9-.1 1.3-.2.4-.1.4-.7 0-.8-1.5-.3-2.6-1.3-3-2.6.5.1 1 .2 1.5.1.4 0 .5-.6.1-.8C5.2 5.5 4.5 4.5 4.5 3.4c0-.4.1-.9.3-1.3 1.8 1.5 4 2.4 6.4 2.5.3 0 .5-.2.5-.5-.2-1.1.4-2.1 1.4-2.4.7-.2 1.4 0 1.9.4.3.2.7.1.9-.1.3-.4.8-.7 1.3-.7.4.1.8.2 1.2.4-.3.3-.5.6-.5 1 0 .3.3.5.5.4z"/></svg>'; return '<svg viewBox="0 0 24 24" width="64" height="64" fill="#4a90c4" aria-hidden="true"><path d="M17.2 1.4c-.8-.1-1.5.2-2 .6C14.3.9 13.2.7 12.1 1c-1.4.4-2.3 1.7-2.1 3.2-2.1-.2-4.2-.9-5.7-2.5-.3-.3-.8-.3-1 0-.6.8-.9 1.7-.9 2.6 0 1.2.5 2.2 1.3 2.9-.3-.1-.6-.1-.8-.3-.4-.2-.9 0-.8.5.3 1.6 1.3 2.8 2.7 3.4-.3 0-.5 0-.8-.1-.4-.1-.8.3-.5.7.8 1.4 2.2 2.3 3.8 2.5-1.3.8-2.8 1.2-4.4 1.2-.5 0-.8.5-.5.9 1.1 1.1 2.9 1.6 5.5 1.6 5.4 0 9.8-4.4 9.8-9.8v-.2c.9-.6 1.6-1.4 2.1-2.4.2-.4-.2-.8-.6-.6-.5.2-1 .3-1.5.4.5-.5.9-1.1 1-1.8.1-.4-.4-.7-.7-.5-.7.4-1.5.6-2.3.7-.8-.7-1.8-1.1-2.9-1.1zm-.8 1.6c.7-.1 1.4.2 1.9.7.2.2.5.3.8.2.3-.1.6-.1.9-.3-.2.3-.5.5-.8.6-.3.1-.4.5-.3.7.2.3.1.5 0 .8v.2c0 4.7-3.8 8.5-8.5 8.5-1.6 0-2.9-.2-3.8-.6 1.7-.3 3.2-1 4.3-2.2.3-.3.1-.8-.3-.8-1.4 0-2.7-.6-3.5-1.7.4 0 .9-.1 1.3-.2.4-.1.4-.7 0-.8-1.5-.3-2.6-1.3-3-2.6.5.1 1 .2 1.5.1.4 0 .5-.6.1-.8C5.2 5.5 4.5 4.5 4.5 3.4c0-.4.1-.9.3-1.3 1.8 1.5 4 2.4 6.4 2.5.3 0 .5-.2.5-.5-.2-1.1.4-2.1 1.4-2.4.7-.2 1.4 0 1.9.4.3.2.7.1.9-.1.3-.4.8-.7 1.3-.7.4.1.8.2 1.2.4-.3.3-.5.6-.5 1 0 .3.3.5.5.4z"/></svg>';
} }
/* ---------- YouTube cards (v1.9.1) ---------- */
function loadYtPlayer(card) {
if (!card || card.getAttribute('data-beep-yt-loaded') === '1') return;
card.setAttribute('data-beep-yt-loaded', '1');
var vid = card.getAttribute('data-beep-yt');
if (!vid) return;
var iframe = document.createElement('iframe');
iframe.src = 'https://www.youtube-nocookie.com/embed/' + encodeURIComponent(vid) + '?autoplay=1&rel=0';
iframe.title = 'YouTube video player';
iframe.allow = 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share';
iframe.allowFullscreen = true;
iframe.setAttribute('referrerpolicy', 'strict-origin-when-cross-origin');
card.innerHTML = '';
card.appendChild(iframe);
}
/* ---------- Click delegation ---------- */ /* ---------- Click delegation ---------- */
function onClick(e) { function onClick(e) {
var t = e.target; var t = e.target;
// YouTube card: click swaps the thumbnail for the inline player
var ytCard = t.closest('.beep-yt-card');
if (ytCard) { e.preventDefault(); loadYtPlayer(ytCard); return; }
var likePost = t.closest('[data-beep-like-post]'); var likePost = t.closest('[data-beep-like-post]');
if (likePost) { e.preventDefault(); if (requireLogin()) handleLike(likePost, 'like/' + likePost.dataset.beepLikePost); return; } if (likePost) { e.preventDefault(); if (requireLogin()) handleLike(likePost, 'like/' + likePost.dataset.beepLikePost); return; }
@@ -602,7 +622,7 @@
// Click anywhere else on a feed post opens the conversation // Click anywhere else on a feed post opens the conversation
var openPost = t.closest('[data-beep-open-thread]'); var openPost = t.closest('[data-beep-open-thread]');
if (openPost && !t.closest('a, button, label, input, textarea, audio, video, .beep-media-grid-item, .beep-gif-embed, [data-beep-reply-form]')) { if (openPost && !t.closest('a, button, label, input, textarea, audio, video, .beep-media-grid-item, .beep-gif-embed, .beep-yt-card, [data-beep-reply-form]')) {
// Don't hijack text selection // Don't hijack text selection
var sel = window.getSelection && window.getSelection(); var sel = window.getSelection && window.getSelection();
if (sel && sel.toString()) return; if (sel && sel.toString()) return;
@@ -636,6 +656,12 @@
} }
function onKeydown(e) { function onKeydown(e) {
// Enter/Space on a focused YouTube card plays it (card has tabindex="0")
if ((e.key === 'Enter' || e.key === ' ') && e.target.matches && e.target.matches('.beep-yt-card')) {
e.preventDefault();
loadYtPlayer(e.target);
return;
}
// Enter in the GIF search box runs the search // Enter in the GIF search box runs the search
if (e.key === 'Enter' && e.target.matches && e.target.matches('[data-beep-gif-search]')) { if (e.key === 'Enter' && e.target.matches && e.target.matches('[data-beep-gif-search]')) {
e.preventDefault(); e.preventDefault();
+2 -2
View File
@@ -2,7 +2,7 @@
/** /**
* Plugin Name: Beep * Plugin Name: Beep
* Description: A Twitter-style microblog for WordPress, with likes, replies, and Gravatars. Use the [beep_feed] shortcode on any page. * Description: A Twitter-style microblog for WordPress, with likes, replies, and Gravatars. Use the [beep_feed] shortcode on any page.
* Version: 1.9.0 * Version: 1.9.1
* Requires at least: 6.0 * Requires at least: 6.0
* Requires PHP: 7.4 * Requires PHP: 7.4
* Author: Sami Ahmed * Author: Sami Ahmed
@@ -14,7 +14,7 @@ if (!defined('ABSPATH')) {
exit; exit;
} }
define('BEEP_VERSION', '1.9.0'); define('BEEP_VERSION', '1.9.1');
define('BEEP_FILE', __FILE__); define('BEEP_FILE', __FILE__);
define('BEEP_DIR', plugin_dir_path(__FILE__)); define('BEEP_DIR', plugin_dir_path(__FILE__));
define('BEEP_URL', plugin_dir_url(__FILE__)); define('BEEP_URL', plugin_dir_url(__FILE__));
+1
View File
@@ -87,6 +87,7 @@ class Beep_Quotes {
'<span class="beep-quoted-time">' . $time . '</span>' . '<span class="beep-quoted-time">' . $time . '</span>' .
'</div>' . '</div>' .
'<div class="beep-quoted-text">' . $content . '</div>' . '<div class="beep-quoted-text">' . $content . '</div>' .
beep_render_youtube_card($quoted['content']) .
'</div></div>'; '</div></div>';
} }
+30
View File
@@ -353,6 +353,7 @@ function beep_render_post($post, $args = []) {
<?php endif; ?> <?php endif; ?>
</div> </div>
<div class="beep-text"><?php echo $content; ?></div> <div class="beep-text"><?php echo $content; ?></div>
<?php echo beep_render_youtube_card($post->post_content); ?>
<?php if (!empty($beep_images)) : ?> <?php if (!empty($beep_images)) : ?>
<div class="beep-media-attachments"> <div class="beep-media-attachments">
<?php echo Beep_Media::render_image_gallery($beep_images); ?> <?php echo Beep_Media::render_image_gallery($beep_images); ?>
@@ -611,6 +612,7 @@ function beep_render_reply($comment, $all_comments = [], $depth = 0, $post_id =
<?php endif; ?> <?php endif; ?>
</div> </div>
<div class="beep-text"><?php echo $content; ?></div> <div class="beep-text"><?php echo $content; ?></div>
<?php echo beep_render_youtube_card($comment->comment_content); ?>
<?php if (!empty($reply_images)) : ?> <?php if (!empty($reply_images)) : ?>
<div class="beep-media-attachments"> <div class="beep-media-attachments">
@@ -710,6 +712,34 @@ function beep_format_content($text) {
return $text; return $text;
} }
/**
* Extract the first YouTube video ID from a text string, if any.
* Supports youtube.com/watch?v=, youtu.be/, shorts/, embed/, live/.
* Returns the 11-char video ID or ''.
*/
function beep_youtube_id_from_text($text) {
if (!preg_match_all('#(?<![\w.-])(?:https?://)?(?:www\.|m\.)?(?:youtube\.com/(?:watch\?[^#\s]*v=|shorts/|embed/|live/)|youtu\.be/)([A-Za-z0-9_-]{11})(?![A-Za-z0-9_-])#i', (string) $text, $m)) {
return '';
}
return $m[1][0];
}
/**
* Render a click-to-play YouTube card for the first YouTube link in $text.
* Thumbnail (hqdefault) + play button; JS swaps in the iframe on click.
* Returns '' when no YouTube link is present.
*/
function beep_render_youtube_card($text) {
$vid = beep_youtube_id_from_text($text);
if ($vid === '') {
return '';
}
return '<div class="beep-yt-card" data-beep-yt="' . esc_attr($vid) . '" role="button" tabindex="0" aria-label="Play embedded YouTube video">'
. '<img class="beep-yt-thumb" src="https://i.ytimg.com/vi/' . esc_attr($vid) . '/hqdefault.jpg" alt="" loading="lazy">'
. '<span class="beep-yt-play"><svg viewBox="0 0 24 24" width="28" height="28" fill="currentColor" aria-hidden="true"><path d="M8 5v14l11-7z"/></svg></span>'
. '</div>';
}
function beep_relative_time($timestamp) { function beep_relative_time($timestamp) {
$now = time(); $now = time();
$diff = $now - $timestamp; $diff = $now - $timestamp;
+4 -1
View File
@@ -4,7 +4,7 @@ Tags: microblog, twitter, tweets, social, comments, polls, elementor
Requires at least: 6.0 Requires at least: 6.0
Tested up to: 6.9 Tested up to: 6.9
Requires PHP: 7.4 Requires PHP: 7.4
Stable tag: 1.9.0 Stable tag: 1.9.1
License: GPLv2 or later License: GPLv2 or later
A Twitter-style microblog for WordPress. Posts, likes, replies, polls, quotes, GIFs, and Gravatars. Use [beep_feed] shortcode or Elementor widget. A Twitter-style microblog for WordPress. Posts, likes, replies, polls, quotes, GIFs, and Gravatars. Use [beep_feed] shortcode or Elementor widget.
@@ -46,6 +46,9 @@ Features:
== Changelog == == Changelog ==
= 1.9.1 =
* YouTube links in beeps, replies, and quoted beeps automatically render as a video card (thumbnail + play button; click to play inline). Supports youtube.com/watch, youtu.be, shorts, embed, and live URLs.
= 1.9.0 = = 1.9.0 =
* Reply composer now supports full media: images (up to 4), GIFs, voice notes, quotes, and polls. The reply form has the same toolbar as the top-level composer. * Reply composer now supports full media: images (up to 4), GIFs, voice notes, quotes, and polls. The reply form has the same toolbar as the top-level composer.
* Replies can be posted with media only — no text required (server gate mirrors the v1.8.1 post rule). * Replies can be posted with media only — no text required (server gate mirrors the v1.8.1 post rule).