Add voice message support: audio upload (MP3/OGG/WAV/WebM/M4A), inline audio player in posts, voice note button + preview in composer

This commit is contained in:
Krystie
2026-05-18 11:57:48 -07:00
parent 07ac9f112d
commit 9f91b0c544
5 changed files with 340 additions and 9 deletions
+88 -6
View File
@@ -65,13 +65,23 @@
input.value = '';
refresh();
// Upload pending images to this new beep
// Upload pending images and voice note to this new beep
var pending = compose._beepPendingImages;
var pendingVoice = compose._beepPendingVoice;
var uploadDone = function () {
reloadFeed(feed, 'recent');
};
var tasks = [];
if (pending && pending.length && data.id) {
uploadPendingImages(data.id, pending, compose).then(function () {
// Refresh the beep in the DOM to show uploaded images
reloadFeed(feed, 'recent');
});
tasks.push(uploadPendingImages(data.id, pending, compose));
}
if (pendingVoice && data.id) {
tasks.push(uploadPendingVoice(data.id, pendingVoice, compose));
}
if (tasks.length) {
Promise.all(tasks).then(uploadDone);
}
})
.catch(function () {
@@ -341,9 +351,57 @@
var input = compose.querySelector('[data-beep-media-input]');
var preview = compose.querySelector('[data-beep-media-preview]');
var counter = compose.querySelector('[data-beep-media-count]');
if (!input) return;
var voiceInput = compose.querySelector('[data-beep-voice-input]');
var voicePreview = compose.querySelector('[data-beep-voice-preview]');
if (!input && !voiceInput) return;
var pendingImages = [];
var pendingVoice = null; // { file, id }
// Voice note input
if (voiceInput) {
voiceInput.addEventListener('change', function (e) {
var files = e.target.files;
if (!files || !files.length) return;
var file = files[0];
if (file.size > 20 * 1024 * 1024) {
window.alert('Voice note must be under 20 MB.');
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.');
voiceInput.value = '';
return;
}
if (pendingVoice) {
window.alert('Only one voice note per beep.');
voiceInput.value = '';
return;
}
var reader = new FileReader();
reader.onload = function (ev) {
var id = 'voice-' + Date.now();
pendingVoice = { file: file, id: id };
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</span><button class="beep-voice-remove" data-beep-voice-remove type="button" title="Remove">&times;</button></div>';
voicePreview.hidden = false;
};
reader.readAsDataURL(file);
voiceInput.value = '';
});
}
// Voice remove button
if (voicePreview) {
voicePreview.addEventListener('click', function (e) {
var btn = e.target.closest('[data-beep-voice-remove]');
if (!btn) return;
pendingVoice = null;
voicePreview.innerHTML = '';
voicePreview.hidden = true;
});
}
input.addEventListener('change', function (e) {
var files = Array.prototype.slice.call(e.target.files);
@@ -397,6 +455,7 @@
});
compose._beepPendingImages = pendingImages;
compose._beepPendingVoice = pendingVoice;
}
function uploadPendingImages(postId, pendingImages, compose) {
@@ -426,6 +485,29 @@
});
}
function uploadPendingVoice(postId, pendingVoice, compose) {
if (!pendingVoice) return Promise.resolve();
var formData = new FormData();
formData.append('file', pendingVoice.file);
return fetch(config.restUrl + 'voice/' + postId, {
method: 'POST',
headers: { 'X-WP-Nonce': config.nonce },
credentials: 'same-origin',
body: formData
})
.then(handleResponse)
.then(function () {
pendingVoice = null;
if (compose) {
var vp = compose.querySelector('[data-beep-voice-preview]');
if (vp) { vp.innerHTML = ''; vp.hidden = true; }
}
})
.catch(function (err) {
console.warn('Voice upload failed:', err);
});
}
function handleResponse(res) {
if (!res.ok) return res.json().then(function (j) { throw j; }, function () { throw res; });
return res.json();