@luckydraw/cumulus 0.28.4 → 0.28.6
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/gateway/adapters/webchat.d.ts.map +1 -1
- package/dist/gateway/adapters/webchat.js +6 -0
- package/dist/gateway/adapters/webchat.js.map +1 -1
- package/dist/gateway/daemon.js +122 -55
- package/dist/gateway/daemon.js.map +1 -1
- package/dist/gateway/gateway-agents-mcp.js +87 -0
- package/dist/gateway/gateway-agents-mcp.js.map +1 -1
- package/dist/gateway/server.d.ts +5 -0
- package/dist/gateway/server.d.ts.map +1 -1
- package/dist/gateway/server.js +31 -0
- package/dist/gateway/server.js.map +1 -1
- package/dist/gateway/static/blex.min.js +34 -11
- package/dist/gateway/static/chat.html +5 -0
- package/dist/gateway/static/icon-192.png +0 -0
- package/dist/gateway/static/icon-192.svg +4 -0
- package/dist/gateway/static/manifest.json +15 -0
- package/dist/gateway/static/widget.js +427 -91
- package/dist/lib/gateway.d.ts.map +1 -1
- package/dist/lib/gateway.js +25 -17
- package/dist/lib/gateway.js.map +1 -1
- package/package.json +1 -1
|
@@ -234,10 +234,13 @@
|
|
|
234
234
|
' display: inline-flex; align-items: center; gap: 0.3em;',
|
|
235
235
|
' background: #2a3a4a; color: #d4e8f8; border: 1px solid #3a5060;',
|
|
236
236
|
' border-radius: 1em; padding: 0.15em 0.6em; font-size: 0.82em;',
|
|
237
|
-
' cursor: default; max-width: 32ch;
|
|
237
|
+
' cursor: default; max-width: 32ch;',
|
|
238
|
+
'}',
|
|
239
|
+
'.cumulus-interaction-chip .chip-label {',
|
|
240
|
+
' overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;',
|
|
238
241
|
'}',
|
|
239
242
|
'.cumulus-interaction-chip .chip-dismiss {',
|
|
240
|
-
' cursor: pointer; opacity: 0.6; font-size: 0.9em; margin-left: 0.2em;',
|
|
243
|
+
' cursor: pointer; opacity: 0.6; font-size: 0.9em; margin-left: 0.2em; flex-shrink: 0;',
|
|
241
244
|
'}',
|
|
242
245
|
'.cumulus-interaction-chip .chip-dismiss:hover { opacity: 1; }',
|
|
243
246
|
|
|
@@ -246,10 +249,13 @@
|
|
|
246
249
|
' display: inline-flex; align-items: center; gap: 0.3em;',
|
|
247
250
|
' background: #2a3a2a; color: #b8e6b8; border: 1px solid #3a6040;',
|
|
248
251
|
' border-radius: 1em; padding: 0.15em 0.6em; font-size: 0.82em;',
|
|
249
|
-
' cursor: default; max-width: 40ch;
|
|
252
|
+
' cursor: default; max-width: 40ch;',
|
|
253
|
+
'}',
|
|
254
|
+
'.cumulus-annotation-chip .chip-label {',
|
|
255
|
+
' overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;',
|
|
250
256
|
'}',
|
|
251
257
|
'.cumulus-annotation-chip .chip-dismiss {',
|
|
252
|
-
' cursor: pointer; opacity: 0.6; font-size: 0.9em; margin-left: 0.2em;',
|
|
258
|
+
' cursor: pointer; opacity: 0.6; font-size: 0.9em; margin-left: 0.2em; flex-shrink: 0;',
|
|
253
259
|
'}',
|
|
254
260
|
'.cumulus-annotation-chip .chip-dismiss:hover { opacity: 1; }',
|
|
255
261
|
|
|
@@ -363,6 +369,46 @@
|
|
|
363
369
|
' font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;',
|
|
364
370
|
'}',
|
|
365
371
|
'.code-block-copy-btn:hover { border-color: #0066cc; color: #ddd; }',
|
|
372
|
+
'.code-block-edit-btn {',
|
|
373
|
+
' background: transparent;',
|
|
374
|
+
' border: 1px solid #555;',
|
|
375
|
+
' border-radius: 0.3em;',
|
|
376
|
+
' color: #aaa;',
|
|
377
|
+
' font-size: 0.8em;',
|
|
378
|
+
' padding: 0.15em 0.55em;',
|
|
379
|
+
' cursor: pointer;',
|
|
380
|
+
' font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;',
|
|
381
|
+
'}',
|
|
382
|
+
'.code-block-edit-btn:hover { border-color: #0066cc; color: #ddd; }',
|
|
383
|
+
'.texitool-embed-container {',
|
|
384
|
+
' border: 1px solid #3a3a3a;',
|
|
385
|
+
' border-radius: 0.4em;',
|
|
386
|
+
' overflow: hidden;',
|
|
387
|
+
' margin: 0.6em 0;',
|
|
388
|
+
' background: #1e1e1e;',
|
|
389
|
+
'}',
|
|
390
|
+
'.texitool-embed-container iframe {',
|
|
391
|
+
' width: 100%; border: none;',
|
|
392
|
+
' min-height: 400px;',
|
|
393
|
+
' display: block;',
|
|
394
|
+
'}',
|
|
395
|
+
'.texitool-embed-actions {',
|
|
396
|
+
' padding: 0.4em 0.85em;',
|
|
397
|
+
' background: #3d3d3d;',
|
|
398
|
+
' border-top: 1px solid #4a4a4a;',
|
|
399
|
+
' display: flex; gap: 8px;',
|
|
400
|
+
'}',
|
|
401
|
+
'.texitool-embed-actions button {',
|
|
402
|
+
' background: transparent;',
|
|
403
|
+
' border: 1px solid #555;',
|
|
404
|
+
' border-radius: 0.3em;',
|
|
405
|
+
' color: #aaa;',
|
|
406
|
+
' font-size: 0.8em;',
|
|
407
|
+
' padding: 0.25em 0.65em;',
|
|
408
|
+
' cursor: pointer;',
|
|
409
|
+
' font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;',
|
|
410
|
+
'}',
|
|
411
|
+
'.texitool-embed-actions button:hover { border-color: #0066cc; color: #ddd; }',
|
|
366
412
|
'.code-block-wrapper pre {',
|
|
367
413
|
' padding: 1em 1.15em; margin: 0;',
|
|
368
414
|
' overflow-x: auto;',
|
|
@@ -1228,7 +1274,23 @@
|
|
|
1228
1274
|
|
|
1229
1275
|
// Per-thread store for blex interaction values (poll answers, confirm clicks, etc.)
|
|
1230
1276
|
// Key: "threadName" → Map<"msgTimestamp:blockIdx", interactionValue>
|
|
1277
|
+
// Backed by localStorage for persistence across page refreshes
|
|
1278
|
+
var BLEX_STORE_KEY = 'cumulus-blex-interactions';
|
|
1231
1279
|
var blexInteractionStore = {};
|
|
1280
|
+
try {
|
|
1281
|
+
var saved = localStorage.getItem(BLEX_STORE_KEY);
|
|
1282
|
+
if (saved) blexInteractionStore = JSON.parse(saved);
|
|
1283
|
+
} catch (e) {
|
|
1284
|
+
/* ignore corrupt/missing localStorage */
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
function _flushBlexStore() {
|
|
1288
|
+
try {
|
|
1289
|
+
localStorage.setItem(BLEX_STORE_KEY, JSON.stringify(blexInteractionStore));
|
|
1290
|
+
} catch (e) {
|
|
1291
|
+
/* quota exceeded or private browsing */
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1232
1294
|
|
|
1233
1295
|
// Extract ~~~blex:TYPE\n{json}\n~~~ fences from text
|
|
1234
1296
|
// Returns { text: string (with placeholders), blocks: Array<{type, json, idx}> }
|
|
@@ -1288,9 +1350,10 @@
|
|
|
1288
1350
|
if (handle && handle.onInteraction) {
|
|
1289
1351
|
// Wire interaction handler
|
|
1290
1352
|
handle.onInteraction(function (interaction) {
|
|
1291
|
-
// Store the interaction value for persistence across re-renders
|
|
1353
|
+
// Store the interaction value for persistence across re-renders + refreshes
|
|
1292
1354
|
if (store && storeKey && interaction.value !== undefined) {
|
|
1293
1355
|
store[storeKey] = interaction.value;
|
|
1356
|
+
_flushBlexStore();
|
|
1294
1357
|
}
|
|
1295
1358
|
|
|
1296
1359
|
// Find the panel — try el.closest first, fall back to document query
|
|
@@ -1319,8 +1382,8 @@
|
|
|
1319
1382
|
sendBtn.click();
|
|
1320
1383
|
}
|
|
1321
1384
|
} else {
|
|
1322
|
-
// Deferred: add to chip tray
|
|
1323
|
-
addInteractionChip(panel, interaction, handle);
|
|
1385
|
+
// Deferred: add to chip tray (sourceId deduplicates rapid-fire from same block)
|
|
1386
|
+
addInteractionChip(panel, interaction, handle, block.type + '-' + idx);
|
|
1324
1387
|
}
|
|
1325
1388
|
}
|
|
1326
1389
|
});
|
|
@@ -1360,36 +1423,45 @@
|
|
|
1360
1423
|
}
|
|
1361
1424
|
|
|
1362
1425
|
// Add a deferred interaction chip to the chip tray
|
|
1363
|
-
|
|
1426
|
+
// If a chip from the same source already exists, replace it (prevents spam from rapid interactions)
|
|
1427
|
+
function addInteractionChip(panel, interaction, handle, sourceId) {
|
|
1364
1428
|
if (!panel) return;
|
|
1365
1429
|
var tray = panel.querySelector('.cumulus-chip-tray');
|
|
1366
1430
|
if (!tray) return;
|
|
1367
1431
|
|
|
1432
|
+
// Replace existing chip from same source instead of appending
|
|
1433
|
+
if (sourceId) {
|
|
1434
|
+
var existing = tray.querySelector(
|
|
1435
|
+
'.cumulus-interaction-chip[data-source="' + sourceId + '"]'
|
|
1436
|
+
);
|
|
1437
|
+
if (existing) existing.remove();
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1368
1440
|
var chip = document.createElement('span');
|
|
1369
1441
|
chip.className = 'cumulus-interaction-chip';
|
|
1370
1442
|
chip.setAttribute('data-serialized', interaction.serialized || '');
|
|
1443
|
+
if (sourceId) chip.setAttribute('data-source', sourceId);
|
|
1371
1444
|
|
|
1372
1445
|
var label = document.createElement('span');
|
|
1446
|
+
label.className = 'chip-label';
|
|
1373
1447
|
label.textContent =
|
|
1374
1448
|
(interaction.icon || '\u2022') +
|
|
1375
1449
|
' ' +
|
|
1376
1450
|
(interaction.summary || interaction.serialized || '').substring(0, 40);
|
|
1377
1451
|
chip.appendChild(label);
|
|
1378
1452
|
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
chip.appendChild(dismiss);
|
|
1392
|
-
}
|
|
1453
|
+
var dismiss = document.createElement('span');
|
|
1454
|
+
dismiss.className = 'chip-dismiss';
|
|
1455
|
+
dismiss.textContent = '\u00d7';
|
|
1456
|
+
dismiss.addEventListener('click', function () {
|
|
1457
|
+
if (interaction.revertable && handle && handle.revertInteraction) {
|
|
1458
|
+
try {
|
|
1459
|
+
handle.revertInteraction(interaction);
|
|
1460
|
+
} catch (e) {}
|
|
1461
|
+
}
|
|
1462
|
+
chip.remove();
|
|
1463
|
+
});
|
|
1464
|
+
chip.appendChild(dismiss);
|
|
1393
1465
|
|
|
1394
1466
|
tray.appendChild(chip);
|
|
1395
1467
|
}
|
|
@@ -1435,17 +1507,30 @@
|
|
|
1435
1507
|
var chip = document.createElement('span');
|
|
1436
1508
|
chip.className = 'cumulus-annotation-chip';
|
|
1437
1509
|
chip.setAttribute('data-serialized', serialized);
|
|
1510
|
+
chip.setAttribute('data-quote', quote);
|
|
1511
|
+
chip.setAttribute('data-comment', comment);
|
|
1512
|
+
chip.setAttribute('data-is-code', isCodeBlock ? '1' : '');
|
|
1513
|
+
chip.setAttribute('data-code-lang', codeLang || '');
|
|
1438
1514
|
chip.title = '"' + quote + '"\n\n' + comment;
|
|
1439
1515
|
|
|
1440
1516
|
var label = document.createElement('span');
|
|
1517
|
+
label.className = 'chip-label';
|
|
1441
1518
|
label.textContent =
|
|
1442
1519
|
'\uD83D\uDCDD \u201C' + truncatedQuote + '\u201D \u2192 ' + truncatedComment;
|
|
1443
1520
|
chip.appendChild(label);
|
|
1444
1521
|
|
|
1522
|
+
// Click label to edit
|
|
1523
|
+
label.style.cursor = 'pointer';
|
|
1524
|
+
label.addEventListener('click', function (e) {
|
|
1525
|
+
e.stopPropagation();
|
|
1526
|
+
showAnnotationEditPopover(panel, chip);
|
|
1527
|
+
});
|
|
1528
|
+
|
|
1445
1529
|
var dismiss = document.createElement('span');
|
|
1446
1530
|
dismiss.className = 'chip-dismiss';
|
|
1447
1531
|
dismiss.textContent = '\u00d7';
|
|
1448
|
-
dismiss.addEventListener('click', function () {
|
|
1532
|
+
dismiss.addEventListener('click', function (e) {
|
|
1533
|
+
e.stopPropagation();
|
|
1449
1534
|
chip.remove();
|
|
1450
1535
|
});
|
|
1451
1536
|
chip.appendChild(dismiss);
|
|
@@ -1453,6 +1538,104 @@
|
|
|
1453
1538
|
tray.appendChild(chip);
|
|
1454
1539
|
}
|
|
1455
1540
|
|
|
1541
|
+
// Show the annotation popover near a chip for editing
|
|
1542
|
+
function showAnnotationEditPopover(panel, chip) {
|
|
1543
|
+
dismissAnnotationPopover(panel);
|
|
1544
|
+
|
|
1545
|
+
var quote = chip.getAttribute('data-quote') || '';
|
|
1546
|
+
var comment = chip.getAttribute('data-comment') || '';
|
|
1547
|
+
var isCodeBlock = chip.getAttribute('data-is-code') === '1';
|
|
1548
|
+
var codeLang = chip.getAttribute('data-code-lang') || '';
|
|
1549
|
+
|
|
1550
|
+
var popover = document.createElement('div');
|
|
1551
|
+
popover.className = 'cumulus-annotation-popover';
|
|
1552
|
+
|
|
1553
|
+
// Position near the chip
|
|
1554
|
+
var panelRect = panel.getBoundingClientRect();
|
|
1555
|
+
var chipRect = chip.getBoundingClientRect();
|
|
1556
|
+
var top = chipRect.top - panelRect.top + panel.scrollTop - 160;
|
|
1557
|
+
if (top < 4) top = chipRect.bottom - panelRect.top + panel.scrollTop + 4;
|
|
1558
|
+
var left = chipRect.left - panelRect.left;
|
|
1559
|
+
if (left + 260 > panelRect.width) left = panelRect.width - 270;
|
|
1560
|
+
if (left < 4) left = 4;
|
|
1561
|
+
popover.style.top = top + 'px';
|
|
1562
|
+
popover.style.left = left + 'px';
|
|
1563
|
+
|
|
1564
|
+
var quoteEl = document.createElement('div');
|
|
1565
|
+
quoteEl.className = 'annotation-quote';
|
|
1566
|
+
quoteEl.textContent = '\u201C' + quote + '\u201D';
|
|
1567
|
+
popover.appendChild(quoteEl);
|
|
1568
|
+
|
|
1569
|
+
var textarea = document.createElement('textarea');
|
|
1570
|
+
textarea.placeholder = 'Edit comment\u2026';
|
|
1571
|
+
textarea.rows = 4;
|
|
1572
|
+
textarea.value = comment;
|
|
1573
|
+
textarea.setAttribute('data-testid', 'annotation-edit-comment');
|
|
1574
|
+
popover.appendChild(textarea);
|
|
1575
|
+
|
|
1576
|
+
var actions = document.createElement('div');
|
|
1577
|
+
actions.className = 'annotation-actions';
|
|
1578
|
+
|
|
1579
|
+
var cancelBtn = document.createElement('button');
|
|
1580
|
+
cancelBtn.className = 'annotation-cancel';
|
|
1581
|
+
cancelBtn.textContent = 'Cancel';
|
|
1582
|
+
cancelBtn.addEventListener('click', function () {
|
|
1583
|
+
dismissAnnotationPopover(panel);
|
|
1584
|
+
});
|
|
1585
|
+
|
|
1586
|
+
var submitBtn = document.createElement('button');
|
|
1587
|
+
submitBtn.className = 'annotation-submit';
|
|
1588
|
+
submitBtn.textContent = 'Update';
|
|
1589
|
+
submitBtn.setAttribute('data-testid', 'annotation-edit-submit');
|
|
1590
|
+
submitBtn.addEventListener('click', function () {
|
|
1591
|
+
var newComment = textarea.value.trim();
|
|
1592
|
+
if (!newComment) {
|
|
1593
|
+
dismissAnnotationPopover(panel);
|
|
1594
|
+
return;
|
|
1595
|
+
}
|
|
1596
|
+
// Update chip data
|
|
1597
|
+
var newSerialized;
|
|
1598
|
+
if (isCodeBlock) {
|
|
1599
|
+
newSerialized =
|
|
1600
|
+
'```' +
|
|
1601
|
+
(codeLang && codeLang !== 'text' ? codeLang : '') +
|
|
1602
|
+
'\n' +
|
|
1603
|
+
quote +
|
|
1604
|
+
'\n```\n' +
|
|
1605
|
+
newComment;
|
|
1606
|
+
} else {
|
|
1607
|
+
newSerialized = '> ' + quote.replace(/\n/g, '\n> ') + '\n' + newComment;
|
|
1608
|
+
}
|
|
1609
|
+
chip.setAttribute('data-serialized', newSerialized);
|
|
1610
|
+
chip.setAttribute('data-comment', newComment);
|
|
1611
|
+
var truncQ = quote.length > 30 ? quote.substring(0, 30) + '\u2026' : quote;
|
|
1612
|
+
var truncC = newComment.length > 30 ? newComment.substring(0, 30) + '\u2026' : newComment;
|
|
1613
|
+
var labelEl = chip.querySelector('.chip-label');
|
|
1614
|
+
if (labelEl) {
|
|
1615
|
+
labelEl.textContent = '\uD83D\uDCDD \u201C' + truncQ + '\u201D \u2192 ' + truncC;
|
|
1616
|
+
}
|
|
1617
|
+
chip.title = '"' + quote + '"\n\n' + newComment;
|
|
1618
|
+
dismissAnnotationPopover(panel);
|
|
1619
|
+
});
|
|
1620
|
+
|
|
1621
|
+
actions.appendChild(cancelBtn);
|
|
1622
|
+
actions.appendChild(submitBtn);
|
|
1623
|
+
popover.appendChild(actions);
|
|
1624
|
+
|
|
1625
|
+
textarea.addEventListener('keydown', function (e) {
|
|
1626
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
1627
|
+
e.preventDefault();
|
|
1628
|
+
submitBtn.click();
|
|
1629
|
+
} else if (e.key === 'Escape') {
|
|
1630
|
+
dismissAnnotationPopover(panel);
|
|
1631
|
+
}
|
|
1632
|
+
});
|
|
1633
|
+
|
|
1634
|
+
panel.appendChild(popover);
|
|
1635
|
+
textarea.focus();
|
|
1636
|
+
textarea.setSelectionRange(textarea.value.length, textarea.value.length);
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1456
1639
|
// Show the annotation popover near a text selection
|
|
1457
1640
|
function showAnnotationPopover(panel, selectedText, anchorRect, isCodeBlock, codeLang) {
|
|
1458
1641
|
// Remove any existing popover
|
|
@@ -1597,7 +1780,7 @@
|
|
|
1597
1780
|
var langLabel = lang.trim() || 'text';
|
|
1598
1781
|
var escapedCode = escapeHtml(code.replace(/\n$/, '')); // trim trailing newline
|
|
1599
1782
|
var tokenId = 'cb' + idx;
|
|
1600
|
-
codeBlocks.push(buildCodeBlock(langLabel, escapedCode, tokenId));
|
|
1783
|
+
codeBlocks.push(buildCodeBlock(langLabel, escapedCode, tokenId, code));
|
|
1601
1784
|
return TOKEN_PREFIX + idx + '\x00';
|
|
1602
1785
|
});
|
|
1603
1786
|
|
|
@@ -1671,16 +1854,36 @@
|
|
|
1671
1854
|
return { html: html, blexBlocks: blexBlocks };
|
|
1672
1855
|
}
|
|
1673
1856
|
|
|
1674
|
-
function
|
|
1857
|
+
function looksLikeUnicodeArt(text) {
|
|
1858
|
+
var boxChars = /[┌┐└┘├┤┬┴┼│─╔╗╚╝╠╣╦╩╬║═┏┓┗┛┣┫┳┻╋┃━╭╮╰╯░▒▓█▶◀▲▼→←↑↓]/;
|
|
1859
|
+
var lines = text.split('\n');
|
|
1860
|
+
var hitCount = 0;
|
|
1861
|
+
for (var i = 0; i < lines.length; i++) {
|
|
1862
|
+
if (boxChars.test(lines[i])) hitCount++;
|
|
1863
|
+
}
|
|
1864
|
+
return hitCount >= 3;
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
function buildCodeBlock(lang, escapedCode, tokenId, rawCode) {
|
|
1868
|
+
var editBtn = '';
|
|
1869
|
+
if (rawCode && looksLikeUnicodeArt(rawCode)) {
|
|
1870
|
+
editBtn =
|
|
1871
|
+
'<button class="code-block-edit-btn" data-edit-target="' +
|
|
1872
|
+
tokenId +
|
|
1873
|
+
'" data-testid="webchat-edit-code">Edit ✎</button>';
|
|
1874
|
+
}
|
|
1675
1875
|
return (
|
|
1676
1876
|
'<div class="code-block-wrapper">' +
|
|
1677
1877
|
'<div class="code-block-header">' +
|
|
1678
1878
|
'<span class="code-block-language">' +
|
|
1679
1879
|
escapeHtml(lang) +
|
|
1680
1880
|
'</span>' +
|
|
1881
|
+
'<span style="display:flex;gap:6px;">' +
|
|
1882
|
+
editBtn +
|
|
1681
1883
|
'<button class="code-block-copy-btn" data-copy-target="' +
|
|
1682
1884
|
tokenId +
|
|
1683
1885
|
'" data-testid="webchat-copy-code">Copy</button>' +
|
|
1886
|
+
'</span>' +
|
|
1684
1887
|
'</div>' +
|
|
1685
1888
|
'<pre><code data-code-id="' +
|
|
1686
1889
|
tokenId +
|
|
@@ -1691,6 +1894,146 @@
|
|
|
1691
1894
|
);
|
|
1692
1895
|
}
|
|
1693
1896
|
|
|
1897
|
+
// Texitool embed URL — change this if self-hosting
|
|
1898
|
+
var TEXITOOL_EMBED_URL = 'https://texi.soapko.com';
|
|
1899
|
+
|
|
1900
|
+
function openTexitoolEditor(wrapper) {
|
|
1901
|
+
var codeEl = wrapper.querySelector('code');
|
|
1902
|
+
if (!codeEl) return;
|
|
1903
|
+
var rawText = codeEl.textContent || '';
|
|
1904
|
+
|
|
1905
|
+
// Build iframe container
|
|
1906
|
+
var container = document.createElement('div');
|
|
1907
|
+
container.className = 'texitool-embed-container';
|
|
1908
|
+
|
|
1909
|
+
var iframe = document.createElement('iframe');
|
|
1910
|
+
var encodedContent = btoa(unescape(encodeURIComponent(rawText)));
|
|
1911
|
+
iframe.src = TEXITOOL_EMBED_URL + '?embed=true&content=' + encodedContent;
|
|
1912
|
+
iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin');
|
|
1913
|
+
iframe.setAttribute('data-testid', 'texitool-embed');
|
|
1914
|
+
container.appendChild(iframe);
|
|
1915
|
+
|
|
1916
|
+
// Action bar
|
|
1917
|
+
var actions = document.createElement('div');
|
|
1918
|
+
actions.className = 'texitool-embed-actions';
|
|
1919
|
+
var saveBtn = document.createElement('button');
|
|
1920
|
+
saveBtn.textContent = 'Save';
|
|
1921
|
+
saveBtn.setAttribute('data-testid', 'texitool-save');
|
|
1922
|
+
var cancelBtn = document.createElement('button');
|
|
1923
|
+
cancelBtn.textContent = 'Cancel';
|
|
1924
|
+
cancelBtn.setAttribute('data-testid', 'texitool-cancel');
|
|
1925
|
+
actions.appendChild(saveBtn);
|
|
1926
|
+
actions.appendChild(cancelBtn);
|
|
1927
|
+
container.appendChild(actions);
|
|
1928
|
+
|
|
1929
|
+
// Store original wrapper for restore
|
|
1930
|
+
var parent = wrapper.parentNode;
|
|
1931
|
+
parent.replaceChild(container, wrapper);
|
|
1932
|
+
|
|
1933
|
+
// Listen for postMessage from Texitool
|
|
1934
|
+
function onMessage(e) {
|
|
1935
|
+
if (!e.data || typeof e.data !== 'object') return;
|
|
1936
|
+
if (e.data.type === 'texitool:save' && e.data.content) {
|
|
1937
|
+
finishEdit(e.data.content);
|
|
1938
|
+
} else if (e.data.type === 'texitool:cancel') {
|
|
1939
|
+
cancelEdit();
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
window.addEventListener('message', onMessage);
|
|
1943
|
+
|
|
1944
|
+
function finishEdit(newContent) {
|
|
1945
|
+
window.removeEventListener('message', onMessage);
|
|
1946
|
+
// Rebuild the code block with updated content
|
|
1947
|
+
var newWrapper = document.createElement('div');
|
|
1948
|
+
newWrapper.innerHTML = buildCodeBlock(
|
|
1949
|
+
'text',
|
|
1950
|
+
escapeHtml(newContent),
|
|
1951
|
+
'edited-' + Date.now(),
|
|
1952
|
+
newContent
|
|
1953
|
+
);
|
|
1954
|
+
var built = newWrapper.firstChild;
|
|
1955
|
+
parent.replaceChild(built, container);
|
|
1956
|
+
wireEditButtons(built);
|
|
1957
|
+
wireCopyButtons(built);
|
|
1958
|
+
// Chip the edited content to the input tray
|
|
1959
|
+
chipEditedContent(newContent, parent);
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
function cancelEdit() {
|
|
1963
|
+
window.removeEventListener('message', onMessage);
|
|
1964
|
+
parent.replaceChild(wrapper, container);
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
// Wire save/cancel buttons as fallback (in case postMessage isn't available)
|
|
1968
|
+
saveBtn.addEventListener('click', function () {
|
|
1969
|
+
// Try to get content from iframe via postMessage request
|
|
1970
|
+
iframe.contentWindow.postMessage({ type: 'texitool:requestSave' }, '*');
|
|
1971
|
+
// Fallback: if no response in 500ms, use original content
|
|
1972
|
+
setTimeout(function () {
|
|
1973
|
+
if (container.parentNode) cancelEdit(); // still mounted = no response
|
|
1974
|
+
}, 500);
|
|
1975
|
+
});
|
|
1976
|
+
|
|
1977
|
+
cancelBtn.addEventListener('click', cancelEdit);
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
function chipEditedContent(content, contextEl) {
|
|
1981
|
+
// Find the nearest thread panel and chip tray
|
|
1982
|
+
var panel = contextEl.closest('.cumulus-thread-panel');
|
|
1983
|
+
if (!panel) panel = document.querySelector('.cumulus-thread-panel');
|
|
1984
|
+
if (!panel) return;
|
|
1985
|
+
var tray = panel.querySelector('.cumulus-chip-tray');
|
|
1986
|
+
if (!tray) return;
|
|
1987
|
+
// Add as an annotation chip with the edited diagram
|
|
1988
|
+
var chip = document.createElement('span');
|
|
1989
|
+
chip.className = 'cumulus-chip cumulus-annotation-chip';
|
|
1990
|
+
chip.style.cssText = 'display:inline-flex;align-items:center;gap:4px;max-width:200px;';
|
|
1991
|
+
var label = document.createElement('span');
|
|
1992
|
+
label.className = 'chip-label';
|
|
1993
|
+
label.style.cssText = 'overflow:hidden;text-overflow:ellipsis;white-space:nowrap;';
|
|
1994
|
+
label.textContent = '✎ edited diagram';
|
|
1995
|
+
chip.appendChild(label);
|
|
1996
|
+
var dismiss = document.createElement('span');
|
|
1997
|
+
dismiss.textContent = '×';
|
|
1998
|
+
dismiss.className = 'chip-dismiss';
|
|
1999
|
+
dismiss.style.cssText = 'cursor:pointer;opacity:0.6;flex-shrink:0;';
|
|
2000
|
+
dismiss.addEventListener('click', function () {
|
|
2001
|
+
chip.remove();
|
|
2002
|
+
});
|
|
2003
|
+
chip.appendChild(dismiss);
|
|
2004
|
+
// Store the content on the chip for collectAndClearChips
|
|
2005
|
+
chip.setAttribute('data-annotation', '```\n' + content + '\n```\nEdited diagram');
|
|
2006
|
+
tray.appendChild(chip);
|
|
2007
|
+
tray.style.display = 'flex';
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
function wireEditButtons(el) {
|
|
2011
|
+
el.querySelectorAll('.code-block-edit-btn').forEach(function (btn) {
|
|
2012
|
+
btn.addEventListener('click', function () {
|
|
2013
|
+
var wrapper = btn.closest('.code-block-wrapper');
|
|
2014
|
+
if (wrapper) openTexitoolEditor(wrapper);
|
|
2015
|
+
});
|
|
2016
|
+
});
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
function wireCopyButtons(el) {
|
|
2020
|
+
el.querySelectorAll('.code-block-copy-btn').forEach(function (btn) {
|
|
2021
|
+
btn.addEventListener('click', function () {
|
|
2022
|
+
var targetId = btn.getAttribute('data-copy-target');
|
|
2023
|
+
var codeEl = el.querySelector('[data-code-id="' + targetId + '"]');
|
|
2024
|
+
if (!codeEl) codeEl = btn.closest('.code-block-wrapper').querySelector('code');
|
|
2025
|
+
if (codeEl && navigator.clipboard) {
|
|
2026
|
+
navigator.clipboard.writeText(codeEl.textContent || '').then(function () {
|
|
2027
|
+
btn.textContent = 'Copied!';
|
|
2028
|
+
setTimeout(function () {
|
|
2029
|
+
btn.textContent = 'Copy';
|
|
2030
|
+
}, 2000);
|
|
2031
|
+
});
|
|
2032
|
+
}
|
|
2033
|
+
});
|
|
2034
|
+
});
|
|
2035
|
+
}
|
|
2036
|
+
|
|
1694
2037
|
function renderTables(html) {
|
|
1695
2038
|
// Split on double newlines to find table blocks
|
|
1696
2039
|
// A table block: lines where most lines start with |
|
|
@@ -2254,20 +2597,8 @@
|
|
|
2254
2597
|
if (!isStreaming) {
|
|
2255
2598
|
renderBlexBlocks(el, mdResult.blexBlocks, false, msgKey, threadName);
|
|
2256
2599
|
}
|
|
2257
|
-
el
|
|
2258
|
-
|
|
2259
|
-
var targetId = btn.getAttribute('data-copy-target');
|
|
2260
|
-
var codeEl = el.querySelector('[data-code-id="' + targetId + '"]');
|
|
2261
|
-
if (codeEl && navigator.clipboard) {
|
|
2262
|
-
navigator.clipboard.writeText(codeEl.textContent || '').then(function () {
|
|
2263
|
-
btn.textContent = 'Copied!';
|
|
2264
|
-
setTimeout(function () {
|
|
2265
|
-
btn.textContent = 'Copy';
|
|
2266
|
-
}, 2000);
|
|
2267
|
-
});
|
|
2268
|
-
}
|
|
2269
|
-
});
|
|
2270
|
-
});
|
|
2600
|
+
wireCopyButtons(el);
|
|
2601
|
+
wireEditButtons(el);
|
|
2271
2602
|
} else if (isStreaming) {
|
|
2272
2603
|
el.innerHTML =
|
|
2273
2604
|
'<span class="cumulus-typing-dots"><span></span><span></span><span></span></span>';
|
|
@@ -3820,20 +4151,8 @@
|
|
|
3820
4151
|
if (!isStreaming) {
|
|
3821
4152
|
renderBlexBlocks(el, mdResult.blexBlocks, false, msgKey, threadName);
|
|
3822
4153
|
}
|
|
3823
|
-
el
|
|
3824
|
-
|
|
3825
|
-
var targetId = btn.getAttribute('data-copy-target');
|
|
3826
|
-
var codeEl = el.querySelector('[data-code-id="' + targetId + '"]');
|
|
3827
|
-
if (codeEl && navigator.clipboard) {
|
|
3828
|
-
navigator.clipboard.writeText(codeEl.textContent || '').then(function () {
|
|
3829
|
-
btn.textContent = 'Copied!';
|
|
3830
|
-
setTimeout(function () {
|
|
3831
|
-
btn.textContent = 'Copy';
|
|
3832
|
-
}, 2000);
|
|
3833
|
-
});
|
|
3834
|
-
}
|
|
3835
|
-
});
|
|
3836
|
-
});
|
|
4154
|
+
wireCopyButtons(el);
|
|
4155
|
+
wireEditButtons(el);
|
|
3837
4156
|
} else if (isStreaming) {
|
|
3838
4157
|
el.innerHTML =
|
|
3839
4158
|
'<span class="cumulus-typing-dots"><span></span><span></span><span></span></span>';
|
|
@@ -4587,55 +4906,72 @@
|
|
|
4587
4906
|
// ── Push notification subscription ──
|
|
4588
4907
|
function registerPushNotifications() {
|
|
4589
4908
|
if (!('serviceWorker' in navigator) || !('PushManager' in window)) {
|
|
4590
|
-
console.log('[Cumulus] Push notifications not supported');
|
|
4909
|
+
console.log('[Cumulus] Push notifications not supported in this browser');
|
|
4591
4910
|
return;
|
|
4592
4911
|
}
|
|
4593
4912
|
|
|
4594
|
-
|
|
4595
|
-
.
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
// Check existing subscription
|
|
4600
|
-
return registration.pushManager.getSubscription().then(function (existing) {
|
|
4601
|
-
if (existing) {
|
|
4602
|
-
// Already subscribed — send to server in case it's new/different
|
|
4603
|
-
sendPushSubscription(existing);
|
|
4604
|
-
return;
|
|
4605
|
-
}
|
|
4913
|
+
if (!('Notification' in window)) {
|
|
4914
|
+
console.log('[Cumulus] Notification API not available');
|
|
4915
|
+
return;
|
|
4916
|
+
}
|
|
4606
4917
|
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4918
|
+
// Must request permission explicitly (required for iOS PWA)
|
|
4919
|
+
Notification.requestPermission().then(function (permission) {
|
|
4920
|
+
console.log('[Cumulus] Notification permission:', permission);
|
|
4921
|
+
if (permission !== 'granted') {
|
|
4922
|
+
console.log('[Cumulus] Push notifications denied by user');
|
|
4923
|
+
return;
|
|
4924
|
+
}
|
|
4925
|
+
|
|
4926
|
+
navigator.serviceWorker
|
|
4927
|
+
.register('/sw.js', { scope: '/' })
|
|
4928
|
+
.then(function (registration) {
|
|
4929
|
+
console.log('[Cumulus] Service worker registered, scope:', registration.scope);
|
|
4930
|
+
|
|
4931
|
+
// Wait for the service worker to be ready
|
|
4932
|
+
return navigator.serviceWorker.ready.then(function (reg) {
|
|
4933
|
+
// Check existing subscription
|
|
4934
|
+
return reg.pushManager.getSubscription().then(function (existing) {
|
|
4935
|
+
if (existing) {
|
|
4936
|
+
console.log('[Cumulus] Existing push subscription found');
|
|
4937
|
+
sendPushSubscription(existing);
|
|
4617
4938
|
return;
|
|
4618
4939
|
}
|
|
4619
4940
|
|
|
4620
|
-
//
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4941
|
+
// Get VAPID key from server
|
|
4942
|
+
var loc = window.location;
|
|
4943
|
+
var apiUrl = loc.protocol + '//' + loc.host;
|
|
4944
|
+
fetch(apiUrl + '/api/push/vapid-key')
|
|
4945
|
+
.then(function (r) {
|
|
4946
|
+
return r.json();
|
|
4947
|
+
})
|
|
4948
|
+
.then(function (data) {
|
|
4949
|
+
if (!data.publicKey) {
|
|
4950
|
+
console.log('[Cumulus] Push not configured on server (no VAPID key)');
|
|
4951
|
+
return;
|
|
4952
|
+
}
|
|
4953
|
+
|
|
4954
|
+
console.log('[Cumulus] Subscribing to push with VAPID key');
|
|
4955
|
+
return reg.pushManager
|
|
4956
|
+
.subscribe({
|
|
4957
|
+
userVisibleOnly: true,
|
|
4958
|
+
applicationServerKey: urlBase64ToUint8Array(data.publicKey),
|
|
4959
|
+
})
|
|
4960
|
+
.then(function (subscription) {
|
|
4961
|
+
console.log('[Cumulus] Push subscription created:', subscription.endpoint);
|
|
4962
|
+
sendPushSubscription(subscription);
|
|
4963
|
+
});
|
|
4625
4964
|
})
|
|
4626
|
-
.
|
|
4627
|
-
console.
|
|
4628
|
-
sendPushSubscription(subscription);
|
|
4965
|
+
.catch(function (err) {
|
|
4966
|
+
console.warn('[Cumulus] Push subscription failed:', err.message || err);
|
|
4629
4967
|
});
|
|
4630
|
-
})
|
|
4631
|
-
.catch(function (err) {
|
|
4632
|
-
console.warn('[Cumulus] Push subscription failed:', err);
|
|
4633
4968
|
});
|
|
4969
|
+
});
|
|
4970
|
+
})
|
|
4971
|
+
.catch(function (err) {
|
|
4972
|
+
console.warn('[Cumulus] Service worker registration failed:', err.message || err);
|
|
4634
4973
|
});
|
|
4635
|
-
|
|
4636
|
-
.catch(function (err) {
|
|
4637
|
-
console.warn('[Cumulus] Service worker registration failed:', err);
|
|
4638
|
-
});
|
|
4974
|
+
});
|
|
4639
4975
|
}
|
|
4640
4976
|
|
|
4641
4977
|
function sendPushSubscription(subscription) {
|