Add media upload JS + CSS: image preview in composer, AJAX upload on submit, Twitter-style gallery grid, video embed styling
This commit is contained in:
+189
@@ -758,3 +758,192 @@
|
||||
opacity: 0.5 !important;
|
||||
cursor: default !important;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Media — images & video
|
||||
============================================================ */
|
||||
|
||||
/* ---------- Composer media button ---------- */
|
||||
.beep-compose-tools {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.beep-media-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
color: var(--beep-accent);
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s ease, color 0.15s ease;
|
||||
}
|
||||
|
||||
.beep-media-btn:hover {
|
||||
background: rgba(29, 155, 240, 0.10);
|
||||
color: var(--beep-accent);
|
||||
}
|
||||
|
||||
.beep-media-btn svg {
|
||||
display: block;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.beep-media-count {
|
||||
font-size: 12px;
|
||||
color: var(--beep-text-secondary);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/* ---------- Composer media preview ---------- */
|
||||
.beep-compose-media-preview {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 4px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.beep-compose-media-preview[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.beep-media-preview-item {
|
||||
position: relative;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
border: 1px solid var(--beep-border);
|
||||
}
|
||||
|
||||
.beep-media-preview-item img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.beep-media-remove {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
transition: background-color 0.15s ease;
|
||||
}
|
||||
|
||||
.beep-media-remove:hover {
|
||||
background: rgba(244, 33, 46, 0.8);
|
||||
}
|
||||
|
||||
/* ---------- Image gallery in posts (Twitter-style grid) ---------- */
|
||||
.beep-media-attachments {
|
||||
margin: 6px 0 2px;
|
||||
}
|
||||
|
||||
.beep-gallery {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--beep-border);
|
||||
}
|
||||
|
||||
.beep-gallery-1 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.beep-gallery-2 {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.beep-gallery-3 {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
}
|
||||
|
||||
.beep-gallery-3 .beep-gallery-item:first-child {
|
||||
grid-row: span 2;
|
||||
}
|
||||
|
||||
.beep-gallery-4 {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
}
|
||||
|
||||
.beep-gallery-item {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: #f0f0f0;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.beep-gallery-item img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
transition: filter 0.15s ease;
|
||||
}
|
||||
|
||||
.beep-gallery-item:hover img {
|
||||
filter: brightness(0.92);
|
||||
}
|
||||
|
||||
/* Lightbox link */
|
||||
.beep-gallery-item a {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* ---------- Video embed ---------- */
|
||||
.beep-video-embed {
|
||||
margin: 6px 0 2px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--beep-border);
|
||||
}
|
||||
|
||||
.beep-video-embed iframe,
|
||||
.beep-video-embed video {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-height: 375px;
|
||||
}
|
||||
|
||||
/* Aspect ratio container for responsive video */
|
||||
.beep-video-embed {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.beep-video-embed iframe {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Make the footer layout handle the new left-aligned tools */
|
||||
.beep-compose-footer {
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
+101
@@ -16,6 +16,7 @@
|
||||
var compose = feed.querySelector('[data-beep-compose]');
|
||||
if (compose) initCompose(compose, feed);
|
||||
initSortTabs(feed);
|
||||
initMedia(compose, feed);
|
||||
feed.addEventListener('click', onClick);
|
||||
feed.addEventListener('input', onInput);
|
||||
feed.addEventListener('keydown', onKeydown);
|
||||
@@ -63,6 +64,15 @@
|
||||
}
|
||||
input.value = '';
|
||||
refresh();
|
||||
|
||||
// Upload pending images to this new beep
|
||||
var pending = compose._beepPendingImages;
|
||||
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');
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
window.alert('Could not post. Please try again.');
|
||||
@@ -325,6 +335,97 @@
|
||||
return Array.from(s).length;
|
||||
}
|
||||
|
||||
|
||||
/* ---------- 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]');
|
||||
if (!input) return;
|
||||
|
||||
var pendingImages = [];
|
||||
|
||||
input.addEventListener('change', function (e) {
|
||||
var files = Array.prototype.slice.call(e.target.files);
|
||||
if (!files.length) return;
|
||||
var room = 4 - pendingImages.length;
|
||||
if (room <= 0) { input.value = ''; return; }
|
||||
files = files.slice(0, room);
|
||||
files.forEach(function (file) {
|
||||
if (file.size > 10 * 1024 * 1024) {
|
||||
window.alert('Image must be under 10 MB: ' + file.name);
|
||||
return;
|
||||
}
|
||||
var reader = new FileReader();
|
||||
reader.onload = function (ev) {
|
||||
var url = ev.target.result;
|
||||
var id = 'pending-' + Date.now() + '-' + Math.random().toString(36).substr(2, 6);
|
||||
pendingImages.push({ file: file, previewUrl: url, id: id });
|
||||
renderPreviews();
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
input.value = '';
|
||||
});
|
||||
|
||||
function renderPreviews() {
|
||||
if (!pendingImages.length) {
|
||||
preview.innerHTML = '';
|
||||
preview.hidden = true;
|
||||
if (counter) counter.hidden = true;
|
||||
return;
|
||||
}
|
||||
preview.hidden = false;
|
||||
if (counter) {
|
||||
counter.textContent = pendingImages.length + '/4';
|
||||
counter.hidden = false;
|
||||
}
|
||||
preview.innerHTML = pendingImages.map(function (img) {
|
||||
return '<div class="beep-media-preview-item" data-preview-id="' + img.id + '">' +
|
||||
'<img src="' + img.previewUrl + '">' +
|
||||
'<button class="beep-media-remove" type="button" title="Remove" data-remove="' + img.id + '">×</button>' +
|
||||
'</div>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
preview.addEventListener('click', function (e) {
|
||||
var btn = e.target.closest('[data-remove]');
|
||||
if (!btn) return;
|
||||
var id = btn.dataset.remove;
|
||||
pendingImages = pendingImages.filter(function (img) { return img.id !== id; });
|
||||
renderPreviews();
|
||||
});
|
||||
|
||||
compose._beepPendingImages = pendingImages;
|
||||
}
|
||||
|
||||
function uploadPendingImages(postId, pendingImages, compose) {
|
||||
if (!pendingImages || !pendingImages.length) return Promise.resolve();
|
||||
var promises = pendingImages.map(function (img) {
|
||||
var formData = new FormData();
|
||||
formData.append('file', img.file);
|
||||
return fetch(config.restUrl + 'media/' + postId, {
|
||||
method: 'POST',
|
||||
headers: { 'X-WP-Nonce': config.nonce },
|
||||
credentials: 'same-origin',
|
||||
body: formData
|
||||
})
|
||||
.then(handleResponse)
|
||||
.catch(function (err) {
|
||||
console.warn('Image upload failed:', img.file.name, err);
|
||||
});
|
||||
});
|
||||
return Promise.all(promises).then(function () {
|
||||
pendingImages.length = 0;
|
||||
if (compose) {
|
||||
var preview = compose.querySelector('[data-beep-media-preview]');
|
||||
if (preview) { preview.innerHTML = ''; preview.hidden = true; }
|
||||
var counter = compose.querySelector('[data-beep-media-count]');
|
||||
if (counter) counter.hidden = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleResponse(res) {
|
||||
if (!res.ok) return res.json().then(function (j) { throw j; }, function () { throw res; });
|
||||
return res.json();
|
||||
|
||||
Reference in New Issue
Block a user