v1.8.1: allow media-only beeps (image/GIF/voice/quote/poll without text)

- PHP: submit_beep accepts empty content when any attachment flag is set
- JS: composer button enables when text is empty but media is attached
- JS: button refreshes on every media state change via custom event
- Version bump + readme changelog
This commit is contained in:
Krystie
2026-07-04 00:41:33 -07:00
parent 5bb39316b1
commit 64344bfef6
4 changed files with 74 additions and 8 deletions
+52 -2
View File
@@ -88,26 +88,53 @@
var button = compose.querySelector('[data-beep-submit]');
if (!input || !button) return;
function hasAttachments() {
var pendingImages = compose._beepPendingImages;
var pendingVoice = compose._beepPendingVoice;
var hasGif = !!compose._beepGifUrl;
var hasQuote = !!(compose._beepQuotedPost && compose._beepQuotedPost.id);
var hasPoll = !!collectPollData(compose);
return (
(pendingImages && pendingImages.length) ||
pendingVoice ||
hasGif ||
hasQuote ||
hasPoll
);
}
function refresh() {
autosize(input);
var remaining = limit - countChars(input.value);
counter.textContent = remaining;
counter.classList.toggle('beep-counter-warn', remaining < 20 && remaining >= 0);
counter.classList.toggle('beep-counter-bad', remaining < 0);
button.disabled = input.value.trim() === '' || remaining < 0;
// Button is enabled when there's text OR any attachment (images, voice, GIF, quote, poll).
button.disabled = !(input.value.trim() !== '' || hasAttachments()) || remaining < 0;
}
input.addEventListener('input', refresh);
// Media / GIF / quote / poll changes also affect whether the button should be enabled.
compose.addEventListener('beep:attachments-changed', refresh);
refresh();
button.addEventListener('click', function () {
var content = input.value.trim();
if (!content) return;
// Allow image-only / media-only posts. Gate is "must have text OR an attachment".
if (!content && !hasAttachments()) return;
button.disabled = true;
var quotedPost = compose._beepQuotedPost;
var gifUrl = compose._beepGifUrl;
var pending = compose._beepPendingImages;
var pendingVoice = compose._beepPendingVoice;
var poll = collectPollData(compose);
var payload = { content: content };
if (quotedPost && quotedPost.id) payload.quote_id = quotedPost.id;
if (gifUrl) payload.gif_url = gifUrl;
// Tell the server what attachments are coming so it can accept empty content.
if (pending && pending.length) payload.has_images = 1;
if (pendingVoice) payload.has_voice = 1;
if (poll) payload.has_poll = 1;
fetch(config.restUrl + 'post', {
method: 'POST',
@@ -128,6 +155,7 @@
clearQuotePreview(compose);
if (compose._beepGifUrl) {
compose._beepGifUrl = null;
compose.dispatchEvent(new CustomEvent('beep:attachments-changed'));
var gifPrev = compose.querySelector('[data-beep-gif-preview]');
if (gifPrev) { gifPrev.innerHTML = ''; gifPrev.hidden = true; }
}
@@ -308,6 +336,7 @@
if (compose) {
compose._beepQuotedPost = { id: quoted.id, data: quoted };
showQuotePreview(compose, quoted);
compose.dispatchEvent(new CustomEvent('beep:attachments-changed'));
}
})
.catch(function() { showToast('Beep not found.', 'error'); });
@@ -344,6 +373,7 @@
var compose = gifRemoveBtn.closest('[data-beep-compose]');
if (compose) {
compose._beepGifUrl = null;
compose.dispatchEvent(new CustomEvent('beep:attachments-changed'));
var preview = compose.querySelector('[data-beep-gif-preview]');
if (preview) { preview.innerHTML = ''; preview.hidden = true; }
}
@@ -695,6 +725,7 @@
compose._beepPendingVoice = pendingVoice;
voicePreview.innerHTML = '<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 ready</span><button class="beep-voice-remove" data-beep-voice-remove type="button" title="Remove">&times;</button></div>';
voicePreview.hidden = false;
if (compose) compose.dispatchEvent(new CustomEvent('beep:attachments-changed'));
};
reader.readAsDataURL(file);
voiceInput.value = '';
@@ -710,6 +741,7 @@
if (compose) compose._beepPendingVoice = null;
voicePreview.innerHTML = '';
voicePreview.hidden = true;
if (compose) compose.dispatchEvent(new CustomEvent('beep:attachments-changed'));
});
}
@@ -731,6 +763,7 @@
var id = 'pending-' + Date.now() + '-' + Math.random().toString(36).substr(2, 6);
pendingImages.push({ file: file, previewUrl: url, id: id });
renderPreviews();
if (compose) compose.dispatchEvent(new CustomEvent('beep:attachments-changed'));
};
reader.readAsDataURL(file);
});
@@ -766,6 +799,7 @@
pendingImages = pendingImages.filter(function (img) { return img.id !== id; });
compose._beepPendingImages = pendingImages;
renderPreviews();
if (compose) compose.dispatchEvent(new CustomEvent('beep:attachments-changed'));
});
}
@@ -773,6 +807,15 @@
compose._beepPendingImages = pendingImages;
compose._beepPendingVoice = pendingVoice;
}
// Poll input changes also affect "has attachments" (poll becomes valid mid-typing).
if (compose) {
compose.addEventListener('input', function (e) {
if (e.target.matches('[data-beep-poll-question], [data-beep-poll-option]')) {
compose.dispatchEvent(new CustomEvent('beep:attachments-changed'));
}
});
}
}
function uploadPendingImages(postId, pendingImages, compose) {
@@ -886,6 +929,7 @@
.then(function(quoted) {
compose._beepQuotedPost = { id: quotedId, data: quoted };
showQuotePreview(compose, quoted);
compose.dispatchEvent(new CustomEvent('beep:attachments-changed'));
})
.catch(function(err) { console.warn('Could not load quoted beep:', err); });
}
@@ -919,6 +963,7 @@
var removeBtn = compose.querySelector('[data-beep-quote-remove]');
if (preview) { preview.innerHTML = ''; preview.hidden = true; }
if (removeBtn) removeBtn.hidden = true;
compose.dispatchEvent(new CustomEvent('beep:attachments-changed'));
}
/* ---------- GIF Picker (Giphy) ---------- */
@@ -989,6 +1034,7 @@
preview.hidden = false;
}
closeGifPicker();
compose.dispatchEvent(new CustomEvent('beep:attachments-changed'));
}
/* ---------- Poll helpers ---------- */
@@ -1005,6 +1051,7 @@
options = pollSection.querySelectorAll('[data-beep-poll-option]');
}
}
compose.dispatchEvent(new CustomEvent('beep:attachments-changed'));
}
function addPollOption(compose) {
@@ -1019,6 +1066,7 @@
newRow.innerHTML = '<input type="text" class="beep-poll-option-input" data-beep-poll-option placeholder="Option ' + (optionCount + 1) + '" maxlength="100">';
optionsContainer.appendChild(newRow);
updatePollButtons(compose);
compose.dispatchEvent(new CustomEvent('beep:attachments-changed'));
}
function removePollOption(compose) {
@@ -1029,6 +1077,7 @@
var lastOption = options[options.length - 1];
if (lastOption) lastOption.parentElement.remove();
updatePollButtons(compose);
compose.dispatchEvent(new CustomEvent('beep:attachments-changed'));
}
function updatePollButtons(compose) {
@@ -1060,6 +1109,7 @@
options[j].value = '';
}
}
compose.dispatchEvent(new CustomEvent('beep:attachments-changed'));
}
function collectPollData(compose) {