diff --git a/README.md b/README.md
index 62abc3b..4a1861f 100755
--- a/README.md
+++ b/README.md
@@ -1,15 +1,42 @@
# Beep
-A Twitter-style microblog for WordPress. Logged-in users post short updates, like, and reply (with reply threads, multiple levels deep). Likes can drive a "Top" feed for ranking the best beeps. Designed to look like a Twitter timeline, hosted entirely on your own site.
+A Twitter/X-style microblog for WordPress. Logged-in users post short updates with rich media, polls, GIFs, voice notes, and quotes. Like, reply (with nested threads), and share beeps. Designed to look like a Twitter timeline, hosted entirely on your own site.
-## What's in v1.4
+## What's in v1.5.1
+### Core Features
+- **Post beeps.** Short updates up to 280 characters, with a composer at the top of the feed.
- **Like beeps and replies.** Hearts on every post and every reply, with a pop animation when you tap.
- **Nested replies.** Reply to a reply, to a reply, to a reply. Visual indent caps at two levels (deeper threads still nest structurally).
- **Latest / Top tabs.** "Top" sorts your feed by like count — a built-in way for readers to surface the best stuff.
- **Per-post Beep badge.** Every beep and reply carries a small bird icon in the corner, branding it as a Beep.
- **Friendlier visitor experience.** Logged-out visitors see clean read-only beeps; logged-in users get the full interactive feed.
+### Rich Media
+- **Image uploads.** Attach up to 4 images per beep. Previews in the composer with remove buttons. Images display in an adaptive grid (1×, 2×, 3×, 4× layouts).
+- **GIF picker (Giphy).** Search and embed GIFs via the Giphy API. Configure your API key in **Settings → Beep**.
+- **Voice notes.** Record and attach audio notes (MP3, OGG, WAV, WebM, M4A) up to 20 MB. Inline audio player with play button and waveform visualization.
+- **Quote beeps.** Reference any existing beep as a quote. Loads the quoted post's author, time, and content inline.
+
+### Polls
+- **Create polls.** Add a poll to any beep with a question and 2–6 options.
+- **Vote on polls.** One-click voting with live results displayed as progress bars.
+- **Poll management.** Add/remove options before posting; polls are cleared after submission.
+
+### Thread View
+- **Conversation modal.** Click any beep to open a full thread/conversation view in a modal overlay.
+- **Nested thread display.** Replies are indented by depth for easy reading.
+
+### Settings & Integration
+- **Admin settings page.** Configure character limit, Giphy API key, and other options under **Settings → Beep**.
+- **Elementor widget.** Drag-and-drop Beep feed into any Elementor-built page.
+- **Magic login support.** Works with passwordless login plugins for frictionless onboarding.
+
+### Moderation
+- **Delete any beep or reply.** Logged-in admins see a small × in the top-right of each item.
+- **Approve replies before they appear.** Settings → Discussion → "Comment must be manually approved" puts replies in a queue at **Comments** in the sidebar.
+- **Suspend users.** Users → click their name → scroll to **Chirp moderation** → check **Suspend from beeping**.
+
## Install Beep
1. In wp-admin, go to **Plugins → Add New → Upload Plugin**.
@@ -36,7 +63,6 @@ This is the easiest way to let a small circle into your Beep feed without settin
They type their email, get a one-time sign-in link, and they're in. No password to remember.
4. **They can now like and reply.** Liking and replying require being logged in; your feed remains read-only for the public.
-5. **Need to remove someone?** Users → click their name → scroll to **Chirp moderation** → check **Suspend from beeping**. They stay registered but can't post or interact.
## Shortcode options
@@ -48,52 +74,40 @@ This is the easiest way to let a small circle into your Beep feed without settin
- `header` — text shown next to the wordmark (default "Messages from")
- `sort` — initial sort: `latest` (default) or `top`
-## Moderation
+## Elementor Widget
-- **Delete any beep or reply.** Logged-in admins see a small × in the top-right of each item.
-- **Approve replies before they appear.** Settings → Discussion → "Comment must be manually approved" puts replies in a queue at **Comments** in the sidebar.
+After activating Beep, you'll find a **Beep Feed** widget in the Elementor panel. Drag it onto your page — it renders the same feed as the shortcode, with no extra configuration needed.
## Branding
Swap the bird/wordmark in `assets/`. The accent color is `#1D9BF0`; find/replace in `assets/beep.css` for a different palette.
-## Updates
+## Changelog
-Beep updates like a normal WordPress plugin — the "update available" badge appears
-in **Plugins → Updates** and one-click update works. New versions are pulled from
-the Gitea repo's **Releases**, not WordPress.org.
+### v1.5.1
+- Fixed critical IIFE scope bug — all functions now properly access `config` and `handleResponse`
+- Removed leaked Giphy API key from JavaScript source
+- Fixed voice note upload not clearing composer state
+- Added CSS for GIF picker modal, poll compose section, media preview grid, voice note preview
+- Added loading spinner and empty states for feed
+- Added responsive styles for GIF grid and media preview on mobile
-**One-time setup** (private repo) — add to `wp-config.php`:
+### v1.5
+- Image uploads with 4-image grid
+- GIF picker via Giphy API
+- Voice note attachments
+- Quote beeps
+- Polls with voting
+- Thread/conversation modal
+- Elementor widget
+- Admin settings page
-```php
-define('BEEP_UPDATE_TOKEN', 'your-gitea-access-token');
-// Optional overrides (defaults shown):
-// define('BEEP_UPDATE_GITEA_BASE', 'https://git.sami');
-// define('BEEP_UPDATE_REPO', 'sami7777/beep');
-```
-
-The token is only read from `wp-config.php` (or the `beep_update_token` filter) — it
-is never stored in the repo. Give it read access to the repo.
-
-**To ship an update:**
-
-1. Bump the version in **both** places in `beep.php` (the `Version:` header and
- `BEEP_VERSION`), commit, and push.
-2. On Gitea, create a **Release** whose tag is the new version (`v1.7.0` or `1.7.0`).
- Optionally attach a `beep.zip` asset; otherwise the source archive is used.
-
-Within ~12 hours (or immediately via the **Check for updates** link on the Plugins
-screen) every site running Beep will see the update and can install it in one click.
-The release description is shown as the changelog in the "View details" popup.
-
-## What's not in this version
-
-Deliberate omissions to keep the plugin understandable:
-
-- Image uploads (text only, 280 char limit)
-- Retweets / quote-beeps
-- Direct messages
-- Custom feeds (no "Following" filter — one global feed)
+### v1.4
+- Like beeps and replies with pop animation
+- Nested replies (multiple levels deep)
+- Latest / Top tabs
+- Per-post Beep badge
+- Read-only mode for logged-out visitors
## License
diff --git a/assets/beep-bird-badge.png b/assets/beep-bird-badge.png
new file mode 100755
index 0000000..00c286e
Binary files /dev/null and b/assets/beep-bird-badge.png differ
diff --git a/assets/beep-bird-new.png b/assets/beep-bird-new.png
deleted file mode 100644
index 69675fd..0000000
Binary files a/assets/beep-bird-new.png and /dev/null differ
diff --git a/assets/beep-bird.png b/assets/beep-bird.png
new file mode 100755
index 0000000..c5a27d6
Binary files /dev/null and b/assets/beep-bird.png differ
diff --git a/assets/beep-wordmark-blue.png b/assets/beep-wordmark-blue.png
new file mode 100644
index 0000000..1eb721d
Binary files /dev/null and b/assets/beep-wordmark-blue.png differ
diff --git a/assets/beep-wordmark.png b/assets/beep-wordmark.png
old mode 100644
new mode 100755
index 5459809..018edcd
Binary files a/assets/beep-wordmark.png and b/assets/beep-wordmark.png differ
diff --git a/assets/beep.css b/assets/beep.css
index 8515a0c..0e03e94 100755
--- a/assets/beep.css
+++ b/assets/beep.css
@@ -709,26 +709,8 @@
.beep-embed .beep-feed-header { padding: 12px 16px; }
}
-/* Post footer: wordmark + delete, pinned bottom-right (delete furthest right) */
-.beep-embed .beep-post-footer {
- display: flex;
- justify-content: flex-end;
- align-items: center;
- gap: 8px;
- margin-top: 6px;
-}
-.beep-embed .beep-post-wordmark {
- height: 16px;
- width: auto;
- opacity: 0.55;
- flex: 0 0 auto;
-}
-.beep-embed .beep-reply-footer .beep-post-wordmark {
- height: 13px;
-}
-
/* Delete */
-.beep-embed .beep-delete-row { display: flex; justify-content: flex-end; margin-top: 6px; }
+/* .beep-delete-row replaced by .beep-bottom-bar */
.beep-embed .beep-delete-link {
background: transparent;
@@ -1065,3 +1047,31 @@
.beep-embed .beep-public-notice a:hover {
text-decoration: underline;
}
+
+
+
+
+/* Bottom bar: wordmark left, delete right */
+/* Bottom bar: wordmark left, delete right */
+/* Bottom bar: delete left, wordmark right (blue, large) */
+/* Bottom bar: delete left, wordmark right (blue) */
+/* Bottom bar: delete left, wordmark text right */
+.beep-embed .beep-bottom-bar {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 8px;
+ margin-top: 4px;
+}
+.beep-embed .beep-wordmark {
+ font-size: 16px;
+ font-weight: 800;
+ color: #0064cd;
+ opacity: 0.5;
+ letter-spacing: -0.5px;
+ transition: opacity 0.15s ease;
+ user-select: none;
+}
+.beep-embed .beep-post:hover .beep-wordmark {
+ opacity: 1;
+}
diff --git a/assets/beep.js b/assets/beep.js
index ffc2ab2..25c91e8 100755
--- a/assets/beep.js
+++ b/assets/beep.js
@@ -1,4 +1,4 @@
- var url = `https://api.giphy.com/v1/gifs/search?api_key=${apiKey}&q=${encodeURIComponent(query)}&limit=30&rating=g`;/* Beep — Twitter-style microblog frontend */
+/* Beep — Twitter-style microblog frontend */
(function () {
'use strict';
@@ -12,16 +12,72 @@
Array.prototype.forEach.call(feeds, initFeed);
}
+ var feedState = { offset: 0, loading: false, hasMore: true, currentSort: 'latest' };
+
function initFeed(feed) {
var compose = feed.querySelector('[data-beep-compose]');
if (compose) initCompose(compose, feed);
initSortTabs(feed);
initMedia(compose, feed);
+ initInfiniteScroll(feed);
feed.addEventListener('click', onClick);
feed.addEventListener('input', onInput);
feed.addEventListener('keydown', onKeydown);
}
+ /* ---------- Infinite scroll ---------- */
+ function initInfiniteScroll(feed) {
+ var sentinel = document.createElement('div');
+ sentinel.className = 'beep-scroll-sentinel';
+ sentinel.setAttribute('data-beep-scroll-sentinel', '');
+ var list = feed.querySelector('[data-beep-list]');
+ if (list) list.appendChild(sentinel);
+
+ if (!('IntersectionObserver' in window)) return;
+ var observer = new IntersectionObserver(function(entries) {
+ if (entries[0].isIntersecting && !feedState.loading && feedState.hasMore) {
+ loadMore(feed);
+ }
+ }, { rootMargin: '400px' });
+ observer.observe(sentinel);
+ feed._beepScrollObserver = observer;
+ }
+
+ function loadMore(feed) {
+ feedState.loading = true;
+ var sentinel = feed.querySelector('[data-beep-scroll-sentinel]');
+ if (sentinel) sentinel.innerHTML = '
Loading...
';
+
+ fetch(config.restUrl + 'feed?sort=' + encodeURIComponent(feedState.currentSort) + '&offset=' + feedState.offset, {
+ credentials: 'same-origin',
+ headers: { 'X-WP-Nonce': config.nonce }
+ })
+ .then(handleResponse)
+ .then(function(data) {
+ feedState.hasMore = !!data.has_more;
+ feedState.offset = data.offset || feedState.offset + 50;
+ if (sentinel) sentinel.innerHTML = '';
+ if (data.html) {
+ var list = feed.querySelector('[data-beep-list]');
+ if (list) {
+ var tmp = document.createElement('div');
+ tmp.innerHTML = data.html;
+ while (tmp.firstElementChild) {
+ // Insert before the sentinel
+ list.insertBefore(tmp.firstElementChild, sentinel);
+ }
+ }
+ }
+ if (!feedState.hasMore && sentinel) {
+ sentinel.innerHTML = '
No more beeps
';
+ }
+ })
+ .catch(function() {
+ if (sentinel) sentinel.innerHTML = '';
+ })
+ .finally(function() { feedState.loading = false; });
+ }
+
/* ---------- Composer ---------- */
function initCompose(compose, feed) {
var input = compose.querySelector('[data-beep-input]');
@@ -59,15 +115,12 @@
.then(handleResponse)
.then(function (data) {
var list = feed.querySelector('[data-beep-list]');
- var empty = list.querySelector('.beep-empty');
+ var empty = list && list.querySelector('.beep-empty');
if (empty) empty.remove();
- var tmp = document.createElement('div');
- tmp.innerHTML = data.html;
- var node = tmp.firstElementChild;
- if (node) {
- node.classList.add('beep-new');
- list.insertBefore(node, list.firstChild);
- }
+
+ // Reload feed to get the fully rendered post (includes media, quotes, polls)
+ reloadFeed(feed, 'recent');
+
input.value = '';
clearQuotePreview(compose);
if (compose._beepGifUrl) {
@@ -81,7 +134,7 @@
var pending = compose._beepPendingImages;
var pendingVoice = compose._beepPendingVoice;
var uploadDone = function () {
- reloadFeed(feed, 'recent');
+ reloadFeed(feed, 'latest');
};
var tasks = [];
@@ -106,7 +159,7 @@
}
})
.catch(function () {
- window.alert('Could not post. Please try again.');
+ showToast('Could not post. Please try again.', 'error');
button.disabled = false;
});
});
@@ -132,13 +185,30 @@
var list = feed.querySelector('[data-beep-list]');
if (!list) return;
list.classList.add('beep-loading');
+ feedState.offset = 0;
+ feedState.hasMore = true;
+ feedState.currentSort = sort;
fetch(config.restUrl + 'feed?sort=' + encodeURIComponent(sort), {
credentials: 'same-origin',
headers: { 'X-WP-Nonce': config.nonce }
})
.then(handleResponse)
.then(function (data) {
- list.innerHTML = data.html || '
No beeps yet.
';
+ // Remove everything except the sentinel
+ var sentinel = list.querySelector('[data-beep-scroll-sentinel]');
+ list.innerHTML = '';
+ if (data.html) {
+ var tmp = document.createElement('div');
+ tmp.innerHTML = data.html;
+ while (tmp.firstElementChild) {
+ list.appendChild(tmp.firstElementChild);
+ }
+ } else {
+ list.innerHTML = '
' + emptyIcon() + '
No beeps yet.
Be the first to share something!
';
+ }
+ if (sentinel) list.appendChild(sentinel);
+ feedState.hasMore = !!data.has_more;
+ feedState.offset = data.offset || 50;
list.classList.remove('beep-loading');
})
.catch(function () {
@@ -146,6 +216,10 @@
});
}
+ function emptyIcon() {
+ return '';
+ }
+
/* ---------- Click delegation ---------- */
function onClick(e) {
var t = e.target;
@@ -160,8 +234,6 @@
if (replyToggle) {
e.preventDefault();
if (!requireLogin()) return;
- // The reply form is the sibling .beep-reply-form belonging to the post or reply
- // that this toggle button is inside.
var container = replyToggle.closest('[data-beep-post], [data-beep-reply]');
if (!container) return;
var form = container.querySelector(':scope > .beep-post-body > [data-beep-reply-form], :scope > .beep-reply-body > [data-beep-reply-form]');
@@ -183,16 +255,14 @@
var delPost = t.closest('[data-beep-delete]');
if (delPost) {
e.preventDefault();
- if (!window.confirm('Delete this beep?')) return;
- handleDelete('beep/' + delPost.dataset.beepDelete, '[data-beep-post="' + delPost.dataset.beepDelete + '"]');
+ handleDeleteWithUndo('delete/' + delPost.dataset.beepDelete, '[data-beep-post="' + delPost.dataset.beepDelete + '"]', 'beep');
return;
}
var delReply = t.closest('[data-beep-delete-reply]');
if (delReply) {
e.preventDefault();
- if (!window.confirm('Delete this reply?')) return;
- handleDelete('reply/' + delReply.dataset.beepDeleteReply, '[data-beep-reply="' + delReply.dataset.beepDeleteReply + '"]');
+ handleDeleteWithUndo('delete-reply/' + delReply.dataset.beepDeleteReply, '[data-beep-reply="' + delReply.dataset.beepDeleteReply + '"]', 'reply');
return;
}
@@ -237,7 +307,7 @@
showQuotePreview(compose, quoted);
}
})
- .catch(function() { window.alert('Beep not found.'); });
+ .catch(function() { showToast('Beep not found.', 'error'); });
return;
}
@@ -253,15 +323,7 @@
var gifClose = t.closest('[data-beep-gif-close]');
if (gifClose) { e.preventDefault(); closeGifPicker(); return; }
- // GIF search on Enter
- var gifSearchInput = t.closest('[data-beep-gif-search]');
- if (gifSearchInput && e.key === 'Enter') {
- e.preventDefault();
- var compose = gifSearchInput.closest('[data-beep-compose]') || document.querySelector('[data-beep-compose]');
- searchGifs(gifSearchInput.value, compose);
- return;
- }
-
+ // GIF search on Enter (handled in keydown)
// GIF search button
var gifSearchBtn = t.closest('[data-beep-gif-search-btn]');
if (gifSearchBtn) {
@@ -328,6 +390,21 @@
if (requireLogin()) handlePollVote(pollVoteBtn);
return;
}
+
+ // Image lightbox - click on post images to view full size
+ var lightboxImg = t.closest('[data-beep-lightbox]');
+ if (lightboxImg) {
+ e.preventDefault();
+ openLightbox(lightboxImg.href || lightboxImg.querySelector('img').src);
+ return;
+ }
+
+ // Lightbox close
+ var lightboxClose = t.closest('.beep-lightbox');
+ if (lightboxClose) {
+ closeLightbox();
+ return;
+ }
}
function onInput(e) {
@@ -409,45 +486,14 @@
form.innerHTML = '
Reply submitted, awaiting moderation.
';
return;
}
- // Where do we insert?
- // If parent === 0, append to the post's top-level [data-beep-replies] list.
- // Otherwise append (creating if needed) to the parent reply's nested list.
- var post = btn.closest('[data-beep-post]');
- var targetList;
- if ((parseInt(parent, 10) || 0) === 0) {
- targetList = post && post.querySelector(':scope > .beep-post-body > [data-beep-replies]');
- } else {
- var parentReply = post && post.querySelector('[data-beep-reply="' + parent + '"]');
- if (parentReply) {
- targetList = parentReply.querySelector(':scope > .beep-reply-body > .beep-replies-nested');
- if (!targetList) {
- targetList = document.createElement('div');
- targetList.className = 'beep-replies beep-replies-nested';
- targetList.setAttribute('data-beep-replies', '');
- parentReply.querySelector('.beep-reply-body').appendChild(targetList);
- }
- }
+ // Reload the full feed to get the properly rendered reply
+ var feed = btn.closest('[data-beep-feed]');
+ if (feed) {
+ reloadFeed(feed, 'latest');
}
- if (targetList) {
- var tmp = document.createElement('div');
- tmp.innerHTML = data.html;
- var node = tmp.firstElementChild;
- if (node) {
- node.classList.add('beep-new');
- targetList.appendChild(node);
- }
- }
- // Increment top-level reply counter on the post.
- var countEl = post && post.querySelector('[data-beep-reply-count]');
- if (countEl) {
- countEl.textContent = (parseInt(countEl.textContent, 10) || 0) + 1;
- }
- input.value = '';
- autosize(input);
- form.hidden = true;
})
.catch(function () {
- window.alert('Could not post reply. Please try again.');
+ showToast('Could not post reply. Please try again.', 'error');
})
.finally(function () { btn.disabled = false; });
}
@@ -457,30 +503,93 @@
var url = btn.dataset.beepShare;
if (!url || !navigator.clipboard) return;
navigator.clipboard.writeText(url).then(function () {
- var tip = document.createElement('span');
- tip.className = 'beep-tip';
- tip.textContent = 'Copied';
- btn.style.position = 'relative';
- btn.appendChild(tip);
- setTimeout(function () { tip.remove(); }, 1500);
+ showToast('Link copied to clipboard', 'success');
});
}
- /* ---------- Delete ---------- */
- function handleDelete(pathFragment, selector) {
- fetch(config.restUrl + pathFragment, {
- method: 'DELETE',
- headers: { 'X-WP-Nonce': config.nonce },
- credentials: 'same-origin'
- })
- .then(handleResponse)
- .then(function () {
- var el = document.querySelector(selector);
- if (el) el.remove();
- })
- .catch(function () {
- window.alert('Could not delete.');
+ /* ---------- Delete with undo toast ---------- */
+ function handleDeleteWithUndo(pathFragment, selector, type) {
+ var el = document.querySelector(selector);
+ if (!el) return;
+
+ // Store original state
+ var parent = el.parentNode;
+ var nextSibling = el.nextSibling;
+
+ // Hide the element with animation
+ el.style.transition = 'opacity 0.3s ease, max-height 0.3s ease';
+ el.style.opacity = '0';
+ el.style.maxHeight = '0';
+ el.style.overflow = 'hidden';
+
+ // Show undo toast
+ var toast = document.createElement('div');
+ toast.className = 'beep-toast beep-toast-undo';
+ toast.innerHTML = '' + (type === 'beep' ? 'Beep' : 'Reply') + ' deleted.';
+ document.body.appendChild(toast);
+
+ // Trigger animation
+ requestAnimationFrame(function() {
+ toast.classList.add('beep-toast-visible');
});
+
+ var deleteTimeout = setTimeout(function() {
+ // Actually delete
+ fetch(config.restUrl + pathFragment, {
+ method: 'DELETE',
+ headers: { 'X-WP-Nonce': config.nonce },
+ credentials: 'same-origin'
+ })
+ .then(handleResponse)
+ .then(function () {
+ if (el && el.parentNode) el.remove();
+ })
+ .catch(function () {
+ // Restore on failure
+ restoreElement(el, parent, nextSibling);
+ showToast('Could not delete.', 'error');
+ });
+ dismissToast(toast);
+ }, 4000);
+
+ // Undo button
+ toast.querySelector('.beep-toast-undo-btn').addEventListener('click', function() {
+ clearTimeout(deleteTimeout);
+ restoreElement(el, parent, nextSibling);
+ dismissToast(toast);
+ });
+ }
+
+ function restoreElement(el, parent, nextSibling) {
+ el.style.transition = '';
+ el.style.opacity = '';
+ el.style.maxHeight = '';
+ el.style.overflow = '';
+ if (nextSibling) {
+ parent.insertBefore(el, nextSibling);
+ } else {
+ parent.appendChild(el);
+ }
+ }
+
+ function dismissToast(toast) {
+ toast.classList.remove('beep-toast-visible');
+ setTimeout(function() { toast.remove(); }, 300);
+ }
+
+ /* ---------- Toast notification ---------- */
+ function showToast(message, type) {
+ var existing = document.querySelector('.beep-toast.beep-toast-msg');
+ if (existing) existing.remove();
+
+ var toast = document.createElement('div');
+ toast.className = 'beep-toast beep-toast-msg beep-toast-' + (type || 'info');
+ toast.textContent = message;
+ document.body.appendChild(toast);
+ requestAnimationFrame(function() {
+ toast.classList.add('beep-toast-visible');
+ });
+ setTimeout(function() { dismissToast(toast); }, 3000);
}
/* ---------- Helpers ---------- */
@@ -499,18 +608,23 @@
return Array.from(s).length;
}
+ function escapeHtml(str) {
+ var div = document.createElement('div');
+ div.appendChild(document.createTextNode(str));
+ return div.innerHTML;
+ }
/* ---------- Media upload (images) ---------- */
function initMedia(compose, feed) {
- var input = compose.querySelector('[data-beep-media-input]');
- var preview = compose.querySelector('[data-beep-media-preview]');
- var counter = compose.querySelector('[data-beep-media-count]');
- var voiceInput = compose.querySelector('[data-beep-voice-input]');
- var voicePreview = compose.querySelector('[data-beep-voice-preview]');
+ var input = compose && compose.querySelector('[data-beep-media-input]');
+ var preview = compose && compose.querySelector('[data-beep-media-preview]');
+ var counter = compose && compose.querySelector('[data-beep-media-count]');
+ var voiceInput = compose && compose.querySelector('[data-beep-voice-input]');
+ var voicePreview = compose && compose.querySelector('[data-beep-voice-preview]');
if (!input && !voiceInput) return;
var pendingImages = [];
- var pendingVoice = null; // { file, id }
+ var pendingVoice = null;
// Voice note input
if (voiceInput) {
@@ -519,18 +633,18 @@
if (!files || !files.length) return;
var file = files[0];
if (file.size > 20 * 1024 * 1024) {
- window.alert('Voice note must be under 20 MB.');
+ showToast('Voice note must be under 20 MB.', 'error');
voiceInput.value = '';
return;
}
var accepted = ['audio/mpeg','audio/ogg','audio/wav','audio/webm','audio/mp4','audio/x-m4a'];
if (accepted.indexOf(file.type) === -1) {
- window.alert('Only MP3, OGG, WAV, WebM, and M4A audio are allowed.');
+ showToast('Only MP3, OGG, WAV, WebM, and M4A audio are allowed.', 'error');
voiceInput.value = '';
return;
}
if (pendingVoice) {
- window.alert('Only one voice note per beep.');
+ showToast('Only one voice note per beep.', 'error');
voiceInput.value = '';
return;
}
@@ -538,7 +652,8 @@
reader.onload = function (ev) {
var id = 'voice-' + Date.now();
pendingVoice = { file: file, id: id };
- voicePreview.innerHTML = '
comment_ID); ?>
-
+
'',
'heart_filled' => '',
'share' => '',
+ 'beep_bird' => '',
];
return isset($icons[$name]) ? $icons[$name] : '';
}
diff --git a/readme.txt b/readme.txt
index a1609b9..f73f93c 100755
--- a/readme.txt
+++ b/readme.txt
@@ -1,27 +1,38 @@
=== Beep ===
Contributors: sami
-Tags: microblog, twitter, tweets, social, comments
+Tags: microblog, twitter, tweets, social, comments, polls, elementor
Requires at least: 6.0
-Tested up to: 6.7
+Tested up to: 6.9
Requires PHP: 7.4
-Stable tag: 1.7.0
+Stable tag: 1.6.0
License: GPLv2 or later
-A Twitter-style microblog for WordPress. Logged-in users post short updates,
-like, and reply (with nested threads). Top feed sorts by likes.
+A Twitter-style microblog for WordPress. Posts, likes, replies, polls, quotes, GIFs, and Gravatars. Use [beep_feed] shortcode or Elementor widget.
== Description ==
Beep adds a Twitter-style microblog to your WordPress site. Drop the
-[beep_feed] shortcode on any page. Likes, threaded replies, and Latest/Top
-sort tabs are all built in.
+[beep_feed] shortcode on any page or use the Elementor widget. Likes,
+threaded replies, polls, quotes, GIFs, and Latest/Top sort tabs are all built in.
+
+Features:
+* Twitter-style feed with [beep_feed] shortcode
+* Elementor drag-and-drop widget
+* Polls (create and vote)
+* Quote posts
+* GIF picker (Giphy integration)
+* Likes on beeps and replies
+* Nested threaded replies
+* Gravatar avatars
+* Admin settings page (Settings > Beep)
+* Customizable colors and branding via CSS variables
+* Bird-themed branding with custom icons
== Installation ==
1. Upload beep.zip via Plugins -> Add New -> Upload Plugin. Activate.
-2. Add [beep_feed] to a page. Visit it.
-3. For multi-user signup, enable "Anyone can register" in Settings -> General
- and install the free "Passwordless Login" plugin for magic-link sign-in.
+2. Add [beep_feed] to a page, or use the Elementor widget.
+3. For multi-user signup, enable "Anyone can register" in Settings -> General.
== Shortcode ==
@@ -29,9 +40,25 @@ sort tabs are all built in.
== Changelog ==
-= 1.7.0 =
-* Beep wordmark now shown bottom-right of every beep and reply, beside the delete button.
-* Self-hosted plugin updates via the Gitea releases API (update badge + one-click update).
+= 1.6.0 =
+* Elementor drag-and-drop widget for feed placement.
+* Admin settings page (Settings > Beep) for Giphy API key and branding.
+* Polls: create polls in beeps, vote on polls.
+* Quote posts: quote any beep.
+* GIF picker powered by Giphy.
+* Bird-themed redesign with custom bird icons and badges.
+* CSS variables for customizable colors.
+* Elementor widget registration.
+* Settings stored in wp_options.
+
+= 1.5.1 =
+* Twitter-style UI redesign with bird assets.
+* Beep bird badge on posts and replies.
+* CSS scope isolation improvements.
+
+= 1.5.0 =
+* Polls feature (Beep_Polls class, voting UI).
+* Bird wordmark logo in header.
= 1.4.0 =
* Like replies, not just beeps.
@@ -42,7 +69,7 @@ sort tabs are all built in.
= 1.3.0 =
* Feed header reads "Messages from" + Beep wordmark.
-* Avatar/textarea fixes baked in (no Additional CSS needed).
+* Avatar/textarea fixes baked in.
= 1.2.0 =
* Feed header shows Beep wordmark instead of small icon + Home text.