Merge branch 'dev'

This commit is contained in:
Jeff Emmett 2026-03-04 14:13:32 -08:00
commit ffa74c3948
1 changed files with 62 additions and 16 deletions

View File

@ -930,16 +930,34 @@ const THREAD_CSS = `
}
.thread-export-menu button:hover { background: rgba(99,102,241,0.15); }
.thread-export-menu button + button { border-top: 1px solid var(--rs-bg-hover); }
.tweet-card__image-bar { display: flex; gap: 0.4rem; margin-top: 0.5rem; }
.tweet-card__image-btn {
padding: 0.25rem 0.5rem; border-radius: 6px; font-size: 0.7rem; font-weight: 600;
cursor: pointer; transition: all 0.15s; background: transparent;
color: var(--rs-text-muted); border: 1px solid var(--rs-input-border);
display: inline-flex; align-items: center; gap: 0.25rem;
.tweet-card__photo-btn {
position: absolute; top: 8px; right: 8px; z-index: 5;
width: 28px; height: 28px; border-radius: 50%; border: 1px solid var(--rs-input-border);
background: var(--rs-bg-surface); color: var(--rs-text-muted); cursor: pointer;
display: flex; align-items: center; justify-content: center;
opacity: 0; transition: opacity 0.15s, border-color 0.15s, color 0.15s;
}
.tweet-card__image-btn:hover { border-color: #6366f1; color: #c4b5fd; }
.tweet-card__image-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.tweet-card__image-btn svg { width: 12px; height: 12px; }
.tweet-card:hover .tweet-card__photo-btn { opacity: 1; }
.tweet-card__photo-btn:hover { border-color: #6366f1; color: #c4b5fd; }
.tweet-card__photo-btn svg { width: 14px; height: 14px; }
.tweet-card__photo-btn .photo-btn-plus {
position: absolute; bottom: -1px; right: -3px; font-size: 10px; font-weight: 700;
color: #6366f1; line-height: 1;
}
.tweet-card__photo-menu {
position: absolute; top: 38px; right: 8px; z-index: 10;
background: var(--rs-bg-surface); border: 1px solid var(--rs-input-border); border-radius: 8px;
min-width: 160px; overflow: hidden; box-shadow: 0 8px 24px var(--rs-shadow-lg);
}
.tweet-card__photo-menu[hidden] { display: none; }
.tweet-card__photo-menu button {
display: flex; align-items: center; gap: 0.4rem; width: 100%;
padding: 0.5rem 0.7rem; border: none; background: transparent;
color: var(--rs-text-primary); font-size: 0.8rem; cursor: pointer; transition: background 0.1s;
}
.tweet-card__photo-menu button:hover { background: rgba(99,102,241,0.15); }
.tweet-card__photo-menu button + button { border-top: 1px solid var(--rs-bg-hover); }
.tweet-card__photo-menu button svg { width: 14px; height: 14px; }
.tweet-card__attached-image { position: relative; margin-top: 0.5rem; border-radius: 8px; overflow: hidden; border: 1px solid var(--rs-input-border); }
.tweet-card__attached-image img { display: block; width: 100%; height: auto; }
.tweet-card__image-remove {
@ -1065,12 +1083,17 @@ function renderThreadBuilderPage(space: string, threadData?: ThreadData | null):
'<button class="tweet-card__image-remove" data-remove-idx="' + i + '" title="Remove image">&#215;</button>' +
'</div>'
: '';
const imageBar = '<div class="tweet-card__image-bar">' +
'<button class="tweet-card__image-btn" data-upload-idx="' + i + '">' + svgUpload + ' Upload</button>' +
'<button class="tweet-card__image-btn" data-generate-idx="' + i + '">' + svgSparkle + ' AI</button>' +
'</div>';
const svgCamera = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>';
const photoBtn = !imgUrl
? '<button class="tweet-card__photo-btn" data-photo-idx="' + i + '" title="Add image">' + svgCamera + '<span class="photo-btn-plus">+</span></button>' +
'<div class="tweet-card__photo-menu" hidden data-menu-idx="' + i + '">' +
'<button data-upload-idx="' + i + '">' + svgUpload + ' Upload</button>' +
'<button data-generate-idx="' + i + '">' + svgSparkle + ' Generate with AI</button>' +
'</div>'
: '';
return '<div class="tweet-card">' +
connector +
photoBtn +
'<div class="tweet-card__header">' +
'<div class="tweet-card__avatar">' + esc(initial) + '</div>' +
'<span class="tweet-card__name">' + esc(name) + '</span>' +
@ -1080,7 +1103,6 @@ function renderThreadBuilderPage(space: string, threadData?: ThreadData | null):
'</div>' +
'<p class="tweet-card__content">' + esc(text) + '</p>' +
imgHtml +
imageBar +
'<div class="tweet-card__footer">' +
'<div class="tweet-card__actions">' +
'<span class="tweet-card__action">' + svgReply + '</span>' +
@ -1382,16 +1404,40 @@ function renderThreadBuilderPage(space: string, threadData?: ThreadData | null):
} catch (e) { console.error('Tweet image removal failed:', e); }
}
// Close any open photo menus
function closeAllPhotoMenus() {
preview.querySelectorAll('.tweet-card__photo-menu').forEach(m => m.hidden = true);
}
// Event delegation on preview container
preview.addEventListener('click', (e) => {
// Photo button → toggle dropdown
const photoBtn = e.target.closest('[data-photo-idx]');
if (photoBtn) {
const idx = photoBtn.dataset.photoIdx;
const menu = preview.querySelector('[data-menu-idx="' + idx + '"]');
if (menu) {
const wasHidden = menu.hidden;
closeAllPhotoMenus();
menu.hidden = !wasHidden;
}
return;
}
const uploadBtn = e.target.closest('[data-upload-idx]');
if (uploadBtn) { tweetImageUploadIdx = uploadBtn.dataset.uploadIdx; tweetImageInput.click(); return; }
if (uploadBtn) { closeAllPhotoMenus(); tweetImageUploadIdx = uploadBtn.dataset.uploadIdx; tweetImageInput.click(); return; }
const genBtn = e.target.closest('[data-generate-idx]');
if (genBtn) { generateTweetImage(genBtn.dataset.generateIdx); return; }
if (genBtn) { closeAllPhotoMenus(); generateTweetImage(genBtn.dataset.generateIdx); return; }
const removeBtn = e.target.closest('[data-remove-idx]');
if (removeBtn) { removeTweetImage(removeBtn.dataset.removeIdx); return; }
});
// Close photo menus on outside click
document.addEventListener('click', (e) => {
if (!e.target.closest('.tweet-card__photo-btn') && !e.target.closest('.tweet-card__photo-menu')) {
closeAllPhotoMenus();
}
});
tweetImageInput.addEventListener('change', () => {
const file = tweetImageInput.files?.[0];
if (file && tweetImageUploadIdx !== null) uploadTweetImage(tweetImageUploadIdx, file);