@beastmode-develeap/beastmode 0.1.35 → 0.1.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +96 -2
- package/dist/index.js.map +1 -1
- package/dist/web/board.html +64 -2
- package/package.json +1 -1
package/dist/web/board.html
CHANGED
|
@@ -2268,7 +2268,8 @@ async function api(method, path, body) {
|
|
|
2268
2268
|
// Each project has its own SQLite DB on the server — without this
|
|
2269
2269
|
// scoping every project would share one global board.
|
|
2270
2270
|
const isBoardCall = path.startsWith('/api/board/items') ||
|
|
2271
|
-
path.startsWith('/api/board/updates')
|
|
2271
|
+
path.startsWith('/api/board/updates') ||
|
|
2272
|
+
path.startsWith('/api/board/attachments');
|
|
2272
2273
|
if (isBoardCall) {
|
|
2273
2274
|
const proj = localStorage.getItem('beastmode-selected-project') || '';
|
|
2274
2275
|
if (proj && proj !== 'all' && !path.includes('board=')) {
|
|
@@ -3205,6 +3206,11 @@ function ItemDetailSidebar({ item, onClose, onStatusChange }) {
|
|
|
3205
3206
|
const [updates, setUpdates] = useState([]);
|
|
3206
3207
|
const [loadingUpdates, setLoadingUpdates] = useState(true);
|
|
3207
3208
|
const [sortNewest, setSortNewest] = useState(true);
|
|
3209
|
+
// Attachments (Gap 8a — 2026-04-15): list every attachment the
|
|
3210
|
+
// board service has for this item, render image types as thumbnails
|
|
3211
|
+
// that open in a new tab when clicked.
|
|
3212
|
+
const [attachments, setAttachments] = useState([]);
|
|
3213
|
+
const [loadingAttachments, setLoadingAttachments] = useState(true);
|
|
3208
3214
|
const sidebarRef = useRef(null);
|
|
3209
3215
|
const topCommentRef = useRef(null);
|
|
3210
3216
|
|
|
@@ -3218,6 +3224,16 @@ function ItemDetailSidebar({ item, onClose, onStatusChange }) {
|
|
|
3218
3224
|
.catch(() => setUpdates([]));
|
|
3219
3225
|
}, [item && item.id]);
|
|
3220
3226
|
|
|
3227
|
+
const refreshAttachments = useCallback(() => {
|
|
3228
|
+
if (!item) return;
|
|
3229
|
+
api('GET', '/api/board/items/' + item.id + '/attachments')
|
|
3230
|
+
.then(data => {
|
|
3231
|
+
const list = Array.isArray(data) ? data : (data.attachments || data.data || []);
|
|
3232
|
+
setAttachments(list);
|
|
3233
|
+
})
|
|
3234
|
+
.catch(() => setAttachments([]));
|
|
3235
|
+
}, [item && item.id]);
|
|
3236
|
+
|
|
3221
3237
|
useEffect(() => {
|
|
3222
3238
|
if (!item) return;
|
|
3223
3239
|
setLoadingUpdates(true);
|
|
@@ -3228,7 +3244,18 @@ function ItemDetailSidebar({ item, onClose, onStatusChange }) {
|
|
|
3228
3244
|
})
|
|
3229
3245
|
.catch(() => setUpdates([]))
|
|
3230
3246
|
.finally(() => setLoadingUpdates(false));
|
|
3231
|
-
|
|
3247
|
+
setLoadingAttachments(true);
|
|
3248
|
+
api('GET', '/api/board/items/' + item.id + '/attachments')
|
|
3249
|
+
.then(data => {
|
|
3250
|
+
const list = Array.isArray(data) ? data : (data.attachments || data.data || []);
|
|
3251
|
+
setAttachments(list);
|
|
3252
|
+
})
|
|
3253
|
+
.catch(() => setAttachments([]))
|
|
3254
|
+
.finally(() => setLoadingAttachments(false));
|
|
3255
|
+
const interval = setInterval(() => {
|
|
3256
|
+
refreshUpdates();
|
|
3257
|
+
refreshAttachments();
|
|
3258
|
+
}, 10000);
|
|
3232
3259
|
return () => clearInterval(interval);
|
|
3233
3260
|
}, [item && item.id]);
|
|
3234
3261
|
|
|
@@ -3357,6 +3384,41 @@ function ItemDetailSidebar({ item, onClose, onStatusChange }) {
|
|
|
3357
3384
|
<label>Parent Epic</label>
|
|
3358
3385
|
<div class="detail-value">${item.parent_epic ? '#' + item.parent_epic : '\u2014'}</div>
|
|
3359
3386
|
</div>
|
|
3387
|
+
${(loadingAttachments || attachments.length > 0) && html`
|
|
3388
|
+
<div style="padding:12px 24px 0;">
|
|
3389
|
+
<h4 style="margin:0 0 8px 0;font-size:13px;font-weight:600;">
|
|
3390
|
+
Attachments${attachments.length > 0 ? ' (' + attachments.length + ')' : ''}
|
|
3391
|
+
</h4>
|
|
3392
|
+
${loadingAttachments ? html`<div class="loading-text">Loading attachments...</div>` : html`
|
|
3393
|
+
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:8px;">
|
|
3394
|
+
${attachments.map((att) => {
|
|
3395
|
+
const isImage = (att.content_type || '').startsWith('image/');
|
|
3396
|
+
const proj = localStorage.getItem('beastmode-selected-project') || '';
|
|
3397
|
+
const sep = '?';
|
|
3398
|
+
const boardQuery = (proj && proj !== 'all') ? sep + 'board=' + encodeURIComponent(proj) : '';
|
|
3399
|
+
const downloadUrl = '/api/board/attachments/' + att.id + '/download' + boardQuery;
|
|
3400
|
+
const sizeLabel = att.file_size > 1024 * 1024
|
|
3401
|
+
? (att.file_size / (1024 * 1024)).toFixed(1) + ' MB'
|
|
3402
|
+
: Math.round(att.file_size / 1024) + ' KB';
|
|
3403
|
+
if (isImage) {
|
|
3404
|
+
return html`
|
|
3405
|
+
<a key=${att.id} href=${downloadUrl} target="_blank" rel="noopener" title=${att.original_name + ' (' + sizeLabel + ')'} style="display:block;border:1px solid var(--border);border-radius:4px;overflow:hidden;background:var(--bg-input);">
|
|
3406
|
+
<img src=${downloadUrl} alt=${att.original_name} style="display:block;width:100%;height:80px;object-fit:cover;background:var(--bg-input);" />
|
|
3407
|
+
<div style="padding:4px 6px;font-size:11px;color:var(--text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${att.original_name}</div>
|
|
3408
|
+
</a>
|
|
3409
|
+
`;
|
|
3410
|
+
}
|
|
3411
|
+
return html`
|
|
3412
|
+
<a key=${att.id} href=${downloadUrl} target="_blank" rel="noopener" title=${att.original_name + ' (' + sizeLabel + ')'} style="display:block;border:1px solid var(--border);border-radius:4px;padding:8px;background:var(--bg-input);text-decoration:none;color:inherit;">
|
|
3413
|
+
<div style="font-size:11px;font-weight:600;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">📎 ${att.original_name}</div>
|
|
3414
|
+
<div style="font-size:10px;color:var(--text-muted);margin-top:2px;">${sizeLabel}</div>
|
|
3415
|
+
</a>
|
|
3416
|
+
`;
|
|
3417
|
+
})}
|
|
3418
|
+
</div>
|
|
3419
|
+
`}
|
|
3420
|
+
</div>
|
|
3421
|
+
`}
|
|
3360
3422
|
<div class="detail-updates">
|
|
3361
3423
|
<${UpdatesHeader} count=${updates.length} sortNewest=${sortNewest} onToggle=${() => setSortNewest(!sortNewest)} itemId=${item.id} onPosted=${refreshUpdates} />
|
|
3362
3424
|
${loadingUpdates ? html`<div class="loading-text">Loading updates...</div>` :
|