@cccarv82/freya 2.20.0 β 3.1.0
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/.agent/rules/freya/agents/analytics-agent.mdc +161 -0
- package/.agent/rules/freya/agents/coach.mdc +94 -62
- package/.agent/rules/freya/agents/ingestor.mdc +99 -101
- package/.agent/rules/freya/agents/master.mdc +140 -67
- package/.agent/rules/freya/agents/oracle.mdc +111 -99
- package/.agent/rules/freya/agents/sm-agent.mdc +133 -0
- package/.agent/rules/freya/freya.mdc +78 -32
- package/cli/web-ui.css +0 -132
- package/cli/web-ui.js +156 -211
- package/cli/web.js +52 -93
- package/package.json +1 -1
- package/templates/base/.agent/rules/freya/agents/analytics-agent.mdc +161 -0
- package/templates/base/.agent/rules/freya/agents/coach.mdc +94 -62
- package/templates/base/.agent/rules/freya/agents/ingestor.mdc +99 -101
- package/templates/base/.agent/rules/freya/agents/master.mdc +140 -67
- package/templates/base/.agent/rules/freya/agents/oracle.mdc +111 -99
- package/templates/base/.agent/rules/freya/agents/sm-agent.mdc +133 -0
- package/templates/base/.agent/rules/freya/freya.mdc +78 -32
package/cli/web-ui.js
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
timelineTag: '',
|
|
22
22
|
chatSessionId: null,
|
|
23
23
|
chatLoaded: false,
|
|
24
|
-
|
|
24
|
+
pendingImage: null // { filename, url, mimeType } from clipboard paste
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
function applyDarkTheme() {
|
|
@@ -196,11 +196,12 @@
|
|
|
196
196
|
var num = 0;
|
|
197
197
|
|
|
198
198
|
// Match append_daily_log / appenddailylog actions
|
|
199
|
-
var logRe = /"type"\s*:\s*"append_?daily_?log"\s*,\s*"text"\s*:\s*"([^"]{1,
|
|
199
|
+
var logRe = /"type"\s*:\s*"append_?daily_?log"\s*,\s*"text"\s*:\s*"([^"]{1,300})/gi;
|
|
200
200
|
var m;
|
|
201
201
|
while ((m = logRe.exec(text)) !== null) {
|
|
202
202
|
num++;
|
|
203
|
-
|
|
203
|
+
var t = m[1].slice(0, 140);
|
|
204
|
+
lines.push(num + '. \u{1F4DD} **Registrar no log:** ' + t + (m[1].length > 140 ? '...' : ''));
|
|
204
205
|
}
|
|
205
206
|
|
|
206
207
|
// Match create_task actions
|
|
@@ -246,8 +247,8 @@
|
|
|
246
247
|
var num = i + 1;
|
|
247
248
|
|
|
248
249
|
if (type === 'appenddailylog') {
|
|
249
|
-
var t = String(a.text || '');
|
|
250
|
-
return num + '. ' + icon + ' **Registrar no log:** ' + t;
|
|
250
|
+
var t = String(a.text || '').slice(0, 140);
|
|
251
|
+
return num + '. ' + icon + ' **Registrar no log:** ' + t + (String(a.text || '').length > 140 ? '...' : '');
|
|
251
252
|
}
|
|
252
253
|
if (type === 'createtask') {
|
|
253
254
|
var desc = String(a.description || '').slice(0, 120);
|
|
@@ -503,32 +504,32 @@
|
|
|
503
504
|
const tag = $('chatModeTag');
|
|
504
505
|
if (tag) { tag.textContent = 'π oracle'; tag.style.display = ''; tag.style.color = 'var(--accent)'; tag.style.borderColor = 'var(--accent)'; }
|
|
505
506
|
|
|
506
|
-
//
|
|
507
|
-
var
|
|
508
|
-
if (
|
|
509
|
-
|
|
510
|
-
}
|
|
511
|
-
if (askAttachments.length) {
|
|
512
|
-
var askHtml = escapeHtml(query).replace(/\n/g, '<br>');
|
|
513
|
-
askHtml += '<div class="bubble-attachments">';
|
|
514
|
-
for (var ai = 0; ai < askAttachments.length; ai++) {
|
|
515
|
-
askHtml += '<img src="/attachments/' + askAttachments[ai].path.replace('data/attachments/', '') + '" class="bubble-img" alt="' + escapeHtml(askAttachments[ai].name) + '" />';
|
|
516
|
-
}
|
|
517
|
-
askHtml += '</div>';
|
|
518
|
-
chatAppend('user', askHtml, { html: true });
|
|
507
|
+
// Include pasted image if present
|
|
508
|
+
var pendingImg = state.pendingImage;
|
|
509
|
+
if (pendingImg) {
|
|
510
|
+
chatAppend('user', escapeHtml(query) + '<br><img src="' + pendingImg.url + '" style="max-height:80px;border-radius:4px;margin-top:6px;cursor:pointer;" onclick="openLightbox(\'' + pendingImg.url + '\')" />', { html: true });
|
|
519
511
|
} else {
|
|
520
512
|
chatAppend('user', query);
|
|
521
513
|
}
|
|
522
|
-
clearPastedImages();
|
|
523
514
|
syncChatThreadVisibility();
|
|
524
|
-
|
|
515
|
+
|
|
516
|
+
// Clear image preview
|
|
517
|
+
var preview = document.getElementById('pastePreview');
|
|
518
|
+
if (preview) preview.remove();
|
|
519
|
+
|
|
520
|
+
setPill('run', 'pesquisandoβ¦');
|
|
525
521
|
|
|
526
522
|
const typingId = 'typing-' + Date.now();
|
|
527
523
|
chatAppend('assistant', '<div class="typing-indicator"><span></span><span></span><span></span></div>', { id: typingId, html: true });
|
|
528
524
|
|
|
529
525
|
try {
|
|
530
526
|
const sessionId = ensureChatSession();
|
|
531
|
-
|
|
527
|
+
var askPayload = { dir: dirOrDefault(), sessionId, query };
|
|
528
|
+
if (pendingImg) {
|
|
529
|
+
askPayload.imagePath = 'data/attachments/' + pendingImg.filename;
|
|
530
|
+
}
|
|
531
|
+
state.pendingImage = null;
|
|
532
|
+
const r = await api('/api/chat/ask', askPayload);
|
|
532
533
|
const answer = r && r.answer ? r.answer : 'NΓ£o encontrei registro';
|
|
533
534
|
|
|
534
535
|
const el = $(typingId);
|
|
@@ -2362,42 +2363,36 @@
|
|
|
2362
2363
|
const tag = $('chatModeTag');
|
|
2363
2364
|
if (tag) { tag.textContent = 'π₯ inbox'; tag.style.display = ''; tag.style.color = 'var(--primary)'; tag.style.borderColor = 'var(--primary)'; }
|
|
2364
2365
|
|
|
2365
|
-
//
|
|
2366
|
-
var
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
setPill('run', 'enviando imagensβ¦');
|
|
2370
|
-
attachments = await uploadPastedImages();
|
|
2371
|
-
}
|
|
2372
|
-
|
|
2373
|
-
// Show user message with image thumbnails
|
|
2374
|
-
var userHtml = escapeHtml(text).replace(/\n/g, '<br>');
|
|
2375
|
-
if (attachments.length) {
|
|
2376
|
-
userHtml += '<div class="bubble-attachments">';
|
|
2377
|
-
for (var ai = 0; ai < attachments.length; ai++) {
|
|
2378
|
-
userHtml += '<img src="/attachments/' + attachments[ai].path.replace('data/attachments/', '') + '" class="bubble-img" alt="' + escapeHtml(attachments[ai].name) + '" />';
|
|
2379
|
-
}
|
|
2380
|
-
userHtml += '</div>';
|
|
2381
|
-
chatAppend('user', userHtml, { html: true });
|
|
2366
|
+
// Include pasted image if present
|
|
2367
|
+
var pendingImg = state.pendingImage;
|
|
2368
|
+
if (pendingImg) {
|
|
2369
|
+
chatAppend('user', escapeHtml(text) + '<br><img src="' + pendingImg.url + '" style="max-height:80px;border-radius:4px;margin-top:6px;cursor:pointer;" onclick="openLightbox(\'' + pendingImg.url + '\')" />', { html: true });
|
|
2382
2370
|
} else {
|
|
2383
2371
|
chatAppend('user', text);
|
|
2384
2372
|
}
|
|
2385
|
-
clearPastedImages();
|
|
2386
2373
|
syncChatThreadVisibility();
|
|
2387
2374
|
|
|
2388
|
-
//
|
|
2389
|
-
var
|
|
2390
|
-
|
|
2375
|
+
// Clear image preview
|
|
2376
|
+
var preview = document.getElementById('pastePreview');
|
|
2377
|
+
if (preview) preview.remove();
|
|
2378
|
+
|
|
2379
|
+
// Show typing indicator while processing
|
|
2380
|
+
var typingId = 'typing-' + Date.now();
|
|
2381
|
+
var typingHtml = '<div class="typing-indicator"><span></span><span></span><span></span></div>';
|
|
2382
|
+
chatAppend('assistant', typingHtml, { id: typingId, html: true });
|
|
2391
2383
|
|
|
2392
2384
|
setPill('run', 'salvandoβ¦');
|
|
2393
|
-
|
|
2385
|
+
var inboxPayload = { dir: dirOrDefault(), text };
|
|
2386
|
+
if (pendingImg) inboxPayload.imagePath = 'data/attachments/' + pendingImg.filename;
|
|
2387
|
+
state.pendingImage = null;
|
|
2388
|
+
await api('/api/inbox/add', inboxPayload);
|
|
2394
2389
|
|
|
2395
|
-
setPill('run',
|
|
2396
|
-
const r = await api('/api/agents/plan', { dir: dirOrDefault(), text
|
|
2390
|
+
setPill('run', 'processandoβ¦');
|
|
2391
|
+
const r = await api('/api/agents/plan', { dir: dirOrDefault(), text });
|
|
2397
2392
|
|
|
2398
2393
|
// Remove typing indicator
|
|
2399
|
-
var
|
|
2400
|
-
if (
|
|
2394
|
+
var typingEl = $(typingId);
|
|
2395
|
+
if (typingEl) typingEl.remove();
|
|
2401
2396
|
|
|
2402
2397
|
state.lastPlan = r.plan || '';
|
|
2403
2398
|
|
|
@@ -2416,12 +2411,8 @@
|
|
|
2416
2411
|
}
|
|
2417
2412
|
|
|
2418
2413
|
if (state.autoApply) {
|
|
2419
|
-
|
|
2420
|
-
chatAppend('assistant', '<div class="typing-indicator"><span></span><span></span><span></span></div>', { id: applyTypingId, html: true });
|
|
2421
|
-
setPill('run', 'aplicandoβ¦');
|
|
2414
|
+
setPill('run', 'applyingβ¦');
|
|
2422
2415
|
await applyPlan();
|
|
2423
|
-
var applyEl = $(applyTypingId);
|
|
2424
|
-
if (applyEl) applyEl.remove();
|
|
2425
2416
|
const a = state.lastApplied || {};
|
|
2426
2417
|
setPill('ok', `applied(${a.tasks || 0}t, ${a.blockers || 0}b)`);
|
|
2427
2418
|
if (state.autoRunReports) {
|
|
@@ -2485,23 +2476,31 @@
|
|
|
2485
2476
|
state.lastApplied = r.applied || null;
|
|
2486
2477
|
const summary = r.applied || {};
|
|
2487
2478
|
|
|
2488
|
-
// Build
|
|
2479
|
+
// Build natural language summary instead of raw JSON
|
|
2489
2480
|
var parts = [];
|
|
2490
|
-
var
|
|
2491
|
-
var
|
|
2492
|
-
|
|
2493
|
-
|
|
2481
|
+
var tc = (summary.tasks || 0), bc = (summary.blockers || 0);
|
|
2482
|
+
var ts = (summary.tasksSkipped || 0), bs = (summary.blockersSkipped || 0);
|
|
2483
|
+
if (tc > 0) parts.push('**' + tc + ' tarefa' + (tc > 1 ? 's' : '') + '** registrada' + (tc > 1 ? 's' : ''));
|
|
2484
|
+
if (bc > 0) parts.push('**' + bc + ' blocker' + (bc > 1 ? 's' : '') + '** registrado' + (bc > 1 ? 's' : ''));
|
|
2485
|
+
if (ts > 0) parts.push(ts + ' tarefa' + (ts > 1 ? 's' : '') + ' jΓ‘ existente' + (ts > 1 ? 's' : '') + ' (ignorada' + (ts > 1 ? 's' : '') + ')');
|
|
2486
|
+
if (bs > 0) parts.push(bs + ' blocker' + (bs > 1 ? 's' : '') + ' jΓ‘ existente' + (bs > 1 ? 's' : '') + ' (ignorado' + (bs > 1 ? 's' : '') + ')');
|
|
2487
|
+
if (summary.mode) parts.push('Modo: **' + summary.mode + '**');
|
|
2494
2488
|
|
|
2495
|
-
|
|
2496
|
-
if (
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
if (tasks === 0 && blockers === 0) parts.push('π InformaΓ§Γ£o registrada no log diΓ‘rio');
|
|
2489
|
+
var oq = summary.oracleQueries;
|
|
2490
|
+
if (oq && Array.isArray(oq) && oq.length > 0) {
|
|
2491
|
+
parts.push(oq.length + ' consulta' + (oq.length > 1 ? 's' : '') + ' realizada' + (oq.length > 1 ? 's' : '') + ' no histΓ³rico');
|
|
2492
|
+
}
|
|
2500
2493
|
|
|
2501
|
-
let msg =
|
|
2494
|
+
let msg = '## Resultado\n\n';
|
|
2495
|
+
if (parts.length > 0) {
|
|
2496
|
+
msg += parts.join(' Β· ') + '\n';
|
|
2497
|
+
} else if (tc === 0 && bc === 0) {
|
|
2498
|
+
msg += 'Contexto registrado no log diΓ‘rio. Nenhuma tarefa ou blocker identificado.\n';
|
|
2499
|
+
}
|
|
2502
2500
|
|
|
2503
2501
|
if (summary && Array.isArray(summary.reportsSuggested) && summary.reportsSuggested.length) {
|
|
2504
|
-
msg += '\n
|
|
2502
|
+
msg += '\n**RelatΓ³rios sugeridos:** ' + summary.reportsSuggested.join(', ');
|
|
2503
|
+
msg += '\n\nUse: **Rodar relatΓ³rios sugeridos** (barra lateral)';
|
|
2505
2504
|
}
|
|
2506
2505
|
|
|
2507
2506
|
setOut(msg);
|
|
@@ -2527,6 +2526,98 @@
|
|
|
2527
2526
|
loadLocal();
|
|
2528
2527
|
wireRailNav();
|
|
2529
2528
|
|
|
2529
|
+
// ββ Image paste (Ctrl+V) support ββ
|
|
2530
|
+
(function setupImagePaste() {
|
|
2531
|
+
var ta = $('inboxText');
|
|
2532
|
+
if (!ta) return;
|
|
2533
|
+
|
|
2534
|
+
ta.addEventListener('paste', async function(e) {
|
|
2535
|
+
var items = e.clipboardData && e.clipboardData.items;
|
|
2536
|
+
if (!items) return;
|
|
2537
|
+
for (var i = 0; i < items.length; i++) {
|
|
2538
|
+
if (items[i].type.indexOf('image') !== -1) {
|
|
2539
|
+
e.preventDefault();
|
|
2540
|
+
var file = items[i].getAsFile();
|
|
2541
|
+
if (!file) return;
|
|
2542
|
+
var reader = new FileReader();
|
|
2543
|
+
reader.onload = async function(ev) {
|
|
2544
|
+
var base64 = ev.target.result.split(',')[1];
|
|
2545
|
+
var mimeType = file.type || 'image/png';
|
|
2546
|
+
try {
|
|
2547
|
+
var r = await api('/api/attachments/upload', { dir: dirOrDefault(), data: base64, mimeType: mimeType });
|
|
2548
|
+
if (r && r.ok) {
|
|
2549
|
+
state.pendingImage = { filename: r.filename, url: r.url, mimeType: mimeType };
|
|
2550
|
+
showImagePreview(r.url, r.filename);
|
|
2551
|
+
}
|
|
2552
|
+
} catch (err) {
|
|
2553
|
+
console.error('Image upload failed:', err);
|
|
2554
|
+
}
|
|
2555
|
+
};
|
|
2556
|
+
reader.readAsDataURL(file);
|
|
2557
|
+
break;
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
});
|
|
2561
|
+
})();
|
|
2562
|
+
|
|
2563
|
+
function showImagePreview(url, filename) {
|
|
2564
|
+
var existing = document.getElementById('pastePreview');
|
|
2565
|
+
if (existing) existing.remove();
|
|
2566
|
+
var ta = $('inboxText');
|
|
2567
|
+
if (!ta) return;
|
|
2568
|
+
var container = document.createElement('div');
|
|
2569
|
+
container.id = 'pastePreview';
|
|
2570
|
+
container.style.cssText = 'display:flex; align-items:center; gap:8px; padding:6px 10px; background:var(--surface); border:1px solid var(--border); border-radius:6px; margin-top:6px;';
|
|
2571
|
+
var img = document.createElement('img');
|
|
2572
|
+
img.src = url;
|
|
2573
|
+
img.style.cssText = 'max-height:48px; max-width:80px; border-radius:4px; cursor:pointer;';
|
|
2574
|
+
img.onclick = function() { openLightbox(url); };
|
|
2575
|
+
var label = document.createElement('span');
|
|
2576
|
+
label.textContent = 'π ' + filename;
|
|
2577
|
+
label.style.cssText = 'font-size:12px; color:var(--muted); flex:1;';
|
|
2578
|
+
var removeBtn = document.createElement('button');
|
|
2579
|
+
removeBtn.textContent = 'β';
|
|
2580
|
+
removeBtn.style.cssText = 'background:none; border:none; color:var(--muted); cursor:pointer; font-size:14px;';
|
|
2581
|
+
removeBtn.onclick = function() { container.remove(); state.pendingImage = null; };
|
|
2582
|
+
container.appendChild(img);
|
|
2583
|
+
container.appendChild(label);
|
|
2584
|
+
container.appendChild(removeBtn);
|
|
2585
|
+
ta.parentElement.insertBefore(container, ta.nextSibling);
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2588
|
+
// ββ Image lightbox modal ββ
|
|
2589
|
+
function openLightbox(url) {
|
|
2590
|
+
var existing = document.getElementById('freyaLightbox');
|
|
2591
|
+
if (existing) existing.remove();
|
|
2592
|
+
var overlay = document.createElement('div');
|
|
2593
|
+
overlay.id = 'freyaLightbox';
|
|
2594
|
+
overlay.style.cssText = 'position:fixed; inset:0; background:rgba(0,0,0,0.85); display:flex; align-items:center; justify-content:center; z-index:99999; cursor:pointer;';
|
|
2595
|
+
overlay.onclick = function(e) { if (e.target === overlay) overlay.remove(); };
|
|
2596
|
+
var img = document.createElement('img');
|
|
2597
|
+
img.src = url;
|
|
2598
|
+
img.style.cssText = 'max-width:90vw; max-height:90vh; border-radius:8px; box-shadow:0 8px 40px rgba(0,0,0,0.6);';
|
|
2599
|
+
var closeBtn = document.createElement('button');
|
|
2600
|
+
closeBtn.textContent = 'β';
|
|
2601
|
+
closeBtn.style.cssText = 'position:absolute; top:20px; right:20px; background:rgba(255,255,255,0.15); border:none; color:white; font-size:24px; width:40px; height:40px; border-radius:50%; cursor:pointer;';
|
|
2602
|
+
closeBtn.onclick = function() { overlay.remove(); };
|
|
2603
|
+
overlay.appendChild(img);
|
|
2604
|
+
overlay.appendChild(closeBtn);
|
|
2605
|
+
document.body.appendChild(overlay);
|
|
2606
|
+
// ESC to close
|
|
2607
|
+
function onEsc(e) { if (e.key === 'Escape') { overlay.remove(); document.removeEventListener('keydown', onEsc); } }
|
|
2608
|
+
document.addEventListener('keydown', onEsc);
|
|
2609
|
+
}
|
|
2610
|
+
window.openLightbox = openLightbox;
|
|
2611
|
+
|
|
2612
|
+
// Make all chat images clickable for lightbox (delegate)
|
|
2613
|
+
document.addEventListener('click', function(e) {
|
|
2614
|
+
var img = e.target;
|
|
2615
|
+
if (img.tagName === 'IMG' && img.closest && img.closest('#chatThread')) {
|
|
2616
|
+
e.preventDefault();
|
|
2617
|
+
openLightbox(img.src);
|
|
2618
|
+
}
|
|
2619
|
+
});
|
|
2620
|
+
|
|
2530
2621
|
// Developer drawer (persist open/close)
|
|
2531
2622
|
try {
|
|
2532
2623
|
const d = $('devDrawer');
|
|
@@ -2616,149 +2707,8 @@
|
|
|
2616
2707
|
refreshToday();
|
|
2617
2708
|
reloadSlugRules();
|
|
2618
2709
|
loadChatHistory();
|
|
2619
|
-
initPasteHandler();
|
|
2620
|
-
|
|
2621
|
-
// Click on chat images β open lightbox
|
|
2622
|
-
var thread = $('chatThread');
|
|
2623
|
-
if (thread) {
|
|
2624
|
-
thread.addEventListener('click', function(e) {
|
|
2625
|
-
if (e.target && e.target.classList && e.target.classList.contains('bubble-img')) {
|
|
2626
|
-
openLightbox(e.target.src, e.target.alt || 'Anexo');
|
|
2627
|
-
}
|
|
2628
|
-
});
|
|
2629
|
-
}
|
|
2630
2710
|
})();
|
|
2631
2711
|
|
|
2632
|
-
// ββ Image Paste Handler ββββββββββββββββββββββββββββββββββββββ
|
|
2633
|
-
function initPasteHandler() {
|
|
2634
|
-
var ta = $('inboxText');
|
|
2635
|
-
if (!ta) return;
|
|
2636
|
-
|
|
2637
|
-
ta.addEventListener('paste', function(e) {
|
|
2638
|
-
var items = e.clipboardData && e.clipboardData.items;
|
|
2639
|
-
if (!items) return;
|
|
2640
|
-
|
|
2641
|
-
for (var i = 0; i < items.length; i++) {
|
|
2642
|
-
if (items[i].type.indexOf('image/') === 0) {
|
|
2643
|
-
e.preventDefault();
|
|
2644
|
-
var file = items[i].getAsFile();
|
|
2645
|
-
if (!file) continue;
|
|
2646
|
-
|
|
2647
|
-
var reader = new FileReader();
|
|
2648
|
-
reader.onload = function(ev) {
|
|
2649
|
-
var ext = file.type.split('/')[1] || 'png';
|
|
2650
|
-
if (ext === 'jpeg') ext = 'jpg';
|
|
2651
|
-
var ts = Date.now();
|
|
2652
|
-
var name = 'paste-' + ts + '.' + ext;
|
|
2653
|
-
state.pastedImages.push({ file: file, dataUrl: ev.target.result, name: name });
|
|
2654
|
-
renderPastePreview();
|
|
2655
|
-
};
|
|
2656
|
-
reader.readAsDataURL(file);
|
|
2657
|
-
break; // handle one image per paste
|
|
2658
|
-
}
|
|
2659
|
-
}
|
|
2660
|
-
});
|
|
2661
|
-
}
|
|
2662
|
-
|
|
2663
|
-
function renderPastePreview() {
|
|
2664
|
-
var strip = $('pastePreview');
|
|
2665
|
-
var inner = $('pastePreviewInner');
|
|
2666
|
-
if (!strip || !inner) return;
|
|
2667
|
-
|
|
2668
|
-
if (!state.pastedImages.length) {
|
|
2669
|
-
strip.style.display = 'none';
|
|
2670
|
-
inner.innerHTML = '';
|
|
2671
|
-
return;
|
|
2672
|
-
}
|
|
2673
|
-
|
|
2674
|
-
strip.style.display = 'block';
|
|
2675
|
-
inner.innerHTML = '';
|
|
2676
|
-
|
|
2677
|
-
for (var i = 0; i < state.pastedImages.length; i++) {
|
|
2678
|
-
(function(idx) {
|
|
2679
|
-
var img = state.pastedImages[idx];
|
|
2680
|
-
var wrap = document.createElement('div');
|
|
2681
|
-
wrap.className = 'paste-thumb';
|
|
2682
|
-
|
|
2683
|
-
var thumb = document.createElement('img');
|
|
2684
|
-
thumb.src = img.dataUrl;
|
|
2685
|
-
thumb.alt = img.name;
|
|
2686
|
-
thumb.style.cssText = 'max-height:60px; max-width:100px; border-radius:6px; border:1px solid var(--border); cursor:pointer;';
|
|
2687
|
-
thumb.title = img.name;
|
|
2688
|
-
thumb.onclick = function() {
|
|
2689
|
-
openLightbox(img.dataUrl, img.name);
|
|
2690
|
-
};
|
|
2691
|
-
|
|
2692
|
-
var removeBtn = document.createElement('button');
|
|
2693
|
-
removeBtn.className = 'paste-thumb-remove';
|
|
2694
|
-
removeBtn.innerHTML = '×';
|
|
2695
|
-
removeBtn.title = 'Remover';
|
|
2696
|
-
removeBtn.onclick = function() {
|
|
2697
|
-
state.pastedImages.splice(idx, 1);
|
|
2698
|
-
renderPastePreview();
|
|
2699
|
-
};
|
|
2700
|
-
|
|
2701
|
-
wrap.appendChild(thumb);
|
|
2702
|
-
wrap.appendChild(removeBtn);
|
|
2703
|
-
inner.appendChild(wrap);
|
|
2704
|
-
})(i);
|
|
2705
|
-
}
|
|
2706
|
-
|
|
2707
|
-
// label
|
|
2708
|
-
var label = document.createElement('span');
|
|
2709
|
-
label.style.cssText = 'font-size:11px; color:var(--faint); margin-left:4px;';
|
|
2710
|
-
label.textContent = state.pastedImages.length + ' imagem(ns) anexada(s)';
|
|
2711
|
-
inner.appendChild(label);
|
|
2712
|
-
}
|
|
2713
|
-
|
|
2714
|
-
// Upload pasted images and return array of { name, path }
|
|
2715
|
-
async function uploadPastedImages() {
|
|
2716
|
-
var results = [];
|
|
2717
|
-
for (var i = 0; i < state.pastedImages.length; i++) {
|
|
2718
|
-
var img = state.pastedImages[i];
|
|
2719
|
-
try {
|
|
2720
|
-
var r = await api('/api/attachments/upload', {
|
|
2721
|
-
dir: dirOrDefault(),
|
|
2722
|
-
data: img.dataUrl,
|
|
2723
|
-
filename: img.name
|
|
2724
|
-
});
|
|
2725
|
-
if (r && r.ok) {
|
|
2726
|
-
results.push({ name: img.name, path: r.path });
|
|
2727
|
-
}
|
|
2728
|
-
} catch(err) {
|
|
2729
|
-
// best-effort, skip failed uploads
|
|
2730
|
-
}
|
|
2731
|
-
}
|
|
2732
|
-
return results;
|
|
2733
|
-
}
|
|
2734
|
-
|
|
2735
|
-
function clearPastedImages() {
|
|
2736
|
-
state.pastedImages = [];
|
|
2737
|
-
renderPastePreview();
|
|
2738
|
-
}
|
|
2739
|
-
|
|
2740
|
-
// ββ Image Lightbox ββββββββββββββββββββββββββββββββββββββββββ
|
|
2741
|
-
function openLightbox(src, name) {
|
|
2742
|
-
var lb = $('imgLightbox');
|
|
2743
|
-
var img = $('imgLightboxImg');
|
|
2744
|
-
var label = $('imgLightboxName');
|
|
2745
|
-
if (!lb || !img) return;
|
|
2746
|
-
img.src = src;
|
|
2747
|
-
if (label) label.textContent = name || '';
|
|
2748
|
-
lb.classList.add('active');
|
|
2749
|
-
}
|
|
2750
|
-
|
|
2751
|
-
function closeLightbox(e) {
|
|
2752
|
-
if (e) e.stopPropagation();
|
|
2753
|
-
var lb = $('imgLightbox');
|
|
2754
|
-
if (lb) lb.classList.remove('active');
|
|
2755
|
-
var img = $('imgLightboxImg');
|
|
2756
|
-
if (img) img.src = '';
|
|
2757
|
-
}
|
|
2758
|
-
|
|
2759
|
-
window.openLightbox = openLightbox;
|
|
2760
|
-
window.closeLightbox = closeLightbox;
|
|
2761
|
-
|
|
2762
2712
|
setPill('ok', 'pronto');
|
|
2763
2713
|
|
|
2764
2714
|
/* ββ Global Keyboard Shortcuts ββ */
|
|
@@ -2775,13 +2725,8 @@
|
|
|
2775
2725
|
openQuickAdd();
|
|
2776
2726
|
return;
|
|
2777
2727
|
}
|
|
2778
|
-
// Escape: Close
|
|
2728
|
+
// Escape: Close quick-add modal or blur active element
|
|
2779
2729
|
if (e.key === 'Escape') {
|
|
2780
|
-
const lb = $('imgLightbox');
|
|
2781
|
-
if (lb && lb.classList.contains('active')) {
|
|
2782
|
-
closeLightbox();
|
|
2783
|
-
return;
|
|
2784
|
-
}
|
|
2785
2730
|
const overlay = $('quickAddOverlay');
|
|
2786
2731
|
if (overlay && overlay.style.display !== 'none') {
|
|
2787
2732
|
closeQuickAdd();
|