watercooler 0.0.7 → 0.0.8
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/package.json +1 -1
- package/public/app.js +153 -26
- package/public/index.html +56 -1
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -1212,7 +1212,9 @@ function updateUI() {
|
|
|
1212
1212
|
// Update recipient select (send panel) - only from coworkers.db
|
|
1213
1213
|
const select = document.getElementById('recipient-select');
|
|
1214
1214
|
const currentVal = select.value;
|
|
1215
|
+
const everyoneOption = recipients.length > 0 ? '<option value="@everyone" style="font-weight: bold; color: #5EEAD4;">@everyone (broadcast to all)</option>' : '';
|
|
1215
1216
|
select.innerHTML = '<option value="">Coworker...</option>' +
|
|
1217
|
+
everyoneOption +
|
|
1216
1218
|
recipients.sort().map(r =>
|
|
1217
1219
|
`<option value="${r}" ${r === currentVal ? 'selected' : ''}>${r}</option>`
|
|
1218
1220
|
).join('');
|
|
@@ -1227,15 +1229,7 @@ function updateUI() {
|
|
|
1227
1229
|
</div>
|
|
1228
1230
|
`;
|
|
1229
1231
|
} else {
|
|
1230
|
-
messagesDiv.innerHTML = messages.slice(0, 20).map(msg =>
|
|
1231
|
-
<div class="message-card ${msg.read ? '' : 'unread'}" data-id="${msg.id}" data-sender="${msg.sender}" data-recipient="${msg.recipient}">
|
|
1232
|
-
<div class="message-header">
|
|
1233
|
-
<span class="message-sender">${msg.sender} → ${msg.recipient}</span>
|
|
1234
|
-
<span class="message-time">${new Date(msg.timestamp).toLocaleString()}</span>
|
|
1235
|
-
</div>
|
|
1236
|
-
<div class="message-text">${marked.parse(msg.message)}</div>
|
|
1237
|
-
</div>
|
|
1238
|
-
`).join('');
|
|
1232
|
+
messagesDiv.innerHTML = messages.slice(0, 20).map(msg => renderMessageCard(msg, true)).join('');
|
|
1239
1233
|
|
|
1240
1234
|
// Add click handlers for all messages (clicking marks as read and sets recipient for reply)
|
|
1241
1235
|
messagesDiv.querySelectorAll('.message-card').forEach(el => {
|
|
@@ -1311,25 +1305,49 @@ async function sendMessage() {
|
|
|
1311
1305
|
}
|
|
1312
1306
|
|
|
1313
1307
|
try {
|
|
1314
|
-
|
|
1315
|
-
method: 'POST',
|
|
1316
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1317
|
-
body: JSON.stringify({ to, from: config.user, message })
|
|
1318
|
-
});
|
|
1308
|
+
let sendPromises;
|
|
1319
1309
|
|
|
1320
|
-
if (
|
|
1310
|
+
if (to === '@everyone') {
|
|
1311
|
+
// Send to all recipients individually
|
|
1312
|
+
sendPromises = recipients.map(recipient =>
|
|
1313
|
+
fetch('/api/send', {
|
|
1314
|
+
method: 'POST',
|
|
1315
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1316
|
+
body: JSON.stringify({ to: recipient, from: config.user, message })
|
|
1317
|
+
})
|
|
1318
|
+
);
|
|
1319
|
+
} else {
|
|
1320
|
+
// Send to single recipient
|
|
1321
|
+
sendPromises = [fetch('/api/send', {
|
|
1322
|
+
method: 'POST',
|
|
1323
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1324
|
+
body: JSON.stringify({ to, from: config.user, message })
|
|
1325
|
+
})];
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
const responses = await Promise.all(sendPromises);
|
|
1329
|
+
const allSuccessful = responses.every(r => r.ok);
|
|
1330
|
+
|
|
1331
|
+
if (allSuccessful) {
|
|
1321
1332
|
// Clear input
|
|
1322
1333
|
document.getElementById('message-input').value = '';
|
|
1323
1334
|
|
|
1324
1335
|
// Show toast
|
|
1325
1336
|
const toast = document.getElementById('toast');
|
|
1337
|
+
const toastMsg = document.getElementById('toast-message');
|
|
1338
|
+
if (toastMsg && to === '@everyone') {
|
|
1339
|
+
toastMsg.textContent = `Message broadcast to ${recipients.length} coworkers!`;
|
|
1340
|
+
}
|
|
1326
1341
|
toast.classList.add('show');
|
|
1327
|
-
setTimeout(() =>
|
|
1342
|
+
setTimeout(() => {
|
|
1343
|
+
toast.classList.remove('show');
|
|
1344
|
+
if (toastMsg) toastMsg.textContent = 'Message sent!';
|
|
1345
|
+
}, 3000);
|
|
1328
1346
|
|
|
1329
1347
|
// Reload data
|
|
1330
1348
|
loadData();
|
|
1331
1349
|
} else {
|
|
1332
|
-
alert('Failed to send message');
|
|
1350
|
+
alert('Failed to send message to some recipients');
|
|
1333
1351
|
}
|
|
1334
1352
|
} catch (err) {
|
|
1335
1353
|
console.error('Error sending:', err);
|
|
@@ -1496,15 +1514,7 @@ function updateDeskDialogContent() {
|
|
|
1496
1514
|
</div>
|
|
1497
1515
|
`;
|
|
1498
1516
|
} else {
|
|
1499
|
-
content.innerHTML = filteredMessages.map(msg =>
|
|
1500
|
-
<div class="message-card ${msg.read ? '' : 'unread'}" style="margin-bottom: 12px;">
|
|
1501
|
-
<div class="message-header">
|
|
1502
|
-
<span class="message-sender">${msg.sender} → ${msg.recipient}</span>
|
|
1503
|
-
<span class="message-time">${new Date(msg.timestamp).toLocaleString()}</span>
|
|
1504
|
-
</div>
|
|
1505
|
-
<div class="message-text">${marked.parse(msg.message)}</div>
|
|
1506
|
-
</div>
|
|
1507
|
-
`).join('');
|
|
1517
|
+
content.innerHTML = filteredMessages.map(msg => renderMessageCard(msg, true)).join('');
|
|
1508
1518
|
}
|
|
1509
1519
|
}
|
|
1510
1520
|
|
|
@@ -1591,6 +1601,123 @@ function updateDeskLabels() {
|
|
|
1591
1601
|
});
|
|
1592
1602
|
}
|
|
1593
1603
|
|
|
1604
|
+
// Parse markdown frontmatter from message text
|
|
1605
|
+
function parseFrontmatter(text) {
|
|
1606
|
+
const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n?/;
|
|
1607
|
+
const match = text.match(frontmatterRegex);
|
|
1608
|
+
|
|
1609
|
+
if (!match) {
|
|
1610
|
+
return { content: text, frontmatter: null };
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
const frontmatterText = match[1];
|
|
1614
|
+
const content = text.slice(match[0].length).trim();
|
|
1615
|
+
|
|
1616
|
+
// Simple YAML-like parsing
|
|
1617
|
+
const frontmatter = {};
|
|
1618
|
+
const lines = frontmatterText.split('\n');
|
|
1619
|
+
|
|
1620
|
+
for (const line of lines) {
|
|
1621
|
+
const colonIndex = line.indexOf(':');
|
|
1622
|
+
if (colonIndex > 0) {
|
|
1623
|
+
const key = line.slice(0, colonIndex).trim();
|
|
1624
|
+
let value = line.slice(colonIndex + 1).trim();
|
|
1625
|
+
|
|
1626
|
+
// Handle arrays: choices: ["option1", "option2"]
|
|
1627
|
+
if (value.startsWith('[') && value.endsWith(']')) {
|
|
1628
|
+
try {
|
|
1629
|
+
value = JSON.parse(value.replace(/'/g, '"'));
|
|
1630
|
+
} catch {
|
|
1631
|
+
// Fallback: parse as comma-separated
|
|
1632
|
+
value = value.slice(1, -1).split(',').map(s => s.trim().replace(/^["']|["']$/g, ''));
|
|
1633
|
+
}
|
|
1634
|
+
} else if (value.startsWith('- ')) {
|
|
1635
|
+
// YAML array format with dashes - collect all consecutive dash items
|
|
1636
|
+
// This is handled below by checking the whole frontmatter
|
|
1637
|
+
} else {
|
|
1638
|
+
// Remove quotes if present
|
|
1639
|
+
value = value.replace(/^["']|["']$/g, '');
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
frontmatter[key] = value;
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
// Handle YAML array format: choices:\n - option1\n - option2
|
|
1647
|
+
const choicesMatch = frontmatterText.match(/choices:\s*\n((?:\s*-\s*[^\n]+\n?)+)/);
|
|
1648
|
+
if (choicesMatch) {
|
|
1649
|
+
const choicesLines = choicesMatch[1].trim().split('\n');
|
|
1650
|
+
frontmatter.choices = choicesLines
|
|
1651
|
+
.map(line => line.replace(/^\s*-\s*/, '').trim())
|
|
1652
|
+
.filter(line => line)
|
|
1653
|
+
.map(choice => choice.replace(/^["']|["']$/g, ''));
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
return { content, frontmatter };
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
// Send a quick response from a choice button
|
|
1660
|
+
window.sendQuickResponse = async function(to, message, messageId) {
|
|
1661
|
+
try {
|
|
1662
|
+
const response = await fetch('/api/send', {
|
|
1663
|
+
method: 'POST',
|
|
1664
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1665
|
+
body: JSON.stringify({ to, from: config.user, message })
|
|
1666
|
+
});
|
|
1667
|
+
|
|
1668
|
+
if (response.ok) {
|
|
1669
|
+
// Mark the original message as read if messageId is provided
|
|
1670
|
+
if (messageId) {
|
|
1671
|
+
await fetch(`/api/messages/${messageId}/read`, { method: 'POST' });
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
// Show toast
|
|
1675
|
+
const toast = document.getElementById('toast');
|
|
1676
|
+
const toastMsg = document.getElementById('toast-message');
|
|
1677
|
+
if (toastMsg) toastMsg.textContent = 'Quick reply sent!';
|
|
1678
|
+
toast.classList.add('show');
|
|
1679
|
+
setTimeout(() => {
|
|
1680
|
+
toast.classList.remove('show');
|
|
1681
|
+
if (toastMsg) toastMsg.textContent = 'Message sent!';
|
|
1682
|
+
}, 2000);
|
|
1683
|
+
|
|
1684
|
+
// Reload data
|
|
1685
|
+
loadData();
|
|
1686
|
+
} else {
|
|
1687
|
+
console.error('Failed to send quick response');
|
|
1688
|
+
}
|
|
1689
|
+
} catch (err) {
|
|
1690
|
+
console.error('Error sending quick response:', err);
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
// Render a message card HTML with optional choice buttons
|
|
1695
|
+
function renderMessageCard(msg, showChoices = true) {
|
|
1696
|
+
const { content, frontmatter } = parseFrontmatter(msg.message);
|
|
1697
|
+
const choices = frontmatter?.choices;
|
|
1698
|
+
const showChoicesButtons = showChoices && Array.isArray(choices) && choices.length > 0;
|
|
1699
|
+
const replyTo = msg.recipient.toLowerCase() === config.user.toLowerCase() ? msg.sender : msg.recipient;
|
|
1700
|
+
|
|
1701
|
+
return `
|
|
1702
|
+
<div class="message-card ${msg.read ? '' : 'unread'}" data-id="${msg.id}" data-sender="${msg.sender}" data-recipient="${msg.recipient}">
|
|
1703
|
+
<div class="message-header">
|
|
1704
|
+
<span class="message-sender">${msg.sender} → ${msg.recipient}</span>
|
|
1705
|
+
<span class="message-time">${new Date(msg.timestamp).toLocaleString()}</span>
|
|
1706
|
+
</div>
|
|
1707
|
+
<div class="message-text">${marked.parse(content)}</div>
|
|
1708
|
+
${showChoicesButtons ? `
|
|
1709
|
+
<div class="message-choices">
|
|
1710
|
+
${choices.map((choice) => `
|
|
1711
|
+
<button class="choice-btn" onclick="sendQuickResponse('${replyTo}', '${choice.replace(/'/g, "\\'")}', ${msg.id})">
|
|
1712
|
+
${choice}
|
|
1713
|
+
</button>
|
|
1714
|
+
`).join('')}
|
|
1715
|
+
</div>
|
|
1716
|
+
` : ''}
|
|
1717
|
+
</div>
|
|
1718
|
+
`;
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1594
1721
|
// Event listeners
|
|
1595
1722
|
document.getElementById('send-btn').addEventListener('click', sendMessage);
|
|
1596
1723
|
|
package/public/index.html
CHANGED
|
@@ -592,6 +592,18 @@
|
|
|
592
592
|
.empty-state p {
|
|
593
593
|
font-size: 1rem;
|
|
594
594
|
}
|
|
595
|
+
|
|
596
|
+
/* Choice buttons on mobile */
|
|
597
|
+
.message-choices {
|
|
598
|
+
gap: 6px;
|
|
599
|
+
margin-top: 10px;
|
|
600
|
+
padding-top: 10px;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
.choice-btn {
|
|
604
|
+
padding: 10px 14px;
|
|
605
|
+
font-size: 0.9rem;
|
|
606
|
+
}
|
|
595
607
|
}
|
|
596
608
|
|
|
597
609
|
/* Extra small screens */
|
|
@@ -776,6 +788,49 @@
|
|
|
776
788
|
.tab-badge[style*="display: none"] {
|
|
777
789
|
display: none;
|
|
778
790
|
}
|
|
791
|
+
|
|
792
|
+
/* Message choice buttons */
|
|
793
|
+
.message-choices {
|
|
794
|
+
display: flex;
|
|
795
|
+
flex-wrap: wrap;
|
|
796
|
+
gap: 8px;
|
|
797
|
+
margin-top: 12px;
|
|
798
|
+
padding-top: 12px;
|
|
799
|
+
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
.choice-btn {
|
|
803
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
804
|
+
color: white;
|
|
805
|
+
border: none;
|
|
806
|
+
border-radius: 20px;
|
|
807
|
+
padding: 8px 16px;
|
|
808
|
+
font-size: 0.85rem;
|
|
809
|
+
font-weight: 500;
|
|
810
|
+
cursor: pointer;
|
|
811
|
+
transition: all 0.2s ease;
|
|
812
|
+
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
.choice-btn:hover {
|
|
816
|
+
transform: translateY(-2px);
|
|
817
|
+
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.5);
|
|
818
|
+
background: linear-gradient(135deg, #7b8ff0 0%, #8b5eb5 100%);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
.choice-btn:active {
|
|
822
|
+
transform: translateY(0);
|
|
823
|
+
box-shadow: 0 2px 6px rgba(102, 126, 234, 0.3);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/* Darker variant for unread messages */
|
|
827
|
+
.message-card.unread .choice-btn {
|
|
828
|
+
background: linear-gradient(135deg, #4fd1c5 0%, #667eea 100%);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
.message-card.unread .choice-btn:hover {
|
|
832
|
+
background: linear-gradient(135deg, #5ee4d8 0%, #7b8ff0 100%);
|
|
833
|
+
}
|
|
779
834
|
</style>
|
|
780
835
|
<!-- Markdown parser -->
|
|
781
836
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
@@ -841,7 +896,7 @@
|
|
|
841
896
|
</div>
|
|
842
897
|
</div>
|
|
843
898
|
|
|
844
|
-
<div class="toast" id="toast">Message sent!</div>
|
|
899
|
+
<div class="toast" id="toast"><span id="toast-message">Message sent!</span></div>
|
|
845
900
|
|
|
846
901
|
<script type="importmap">
|
|
847
902
|
{
|