fix: seen-by layout, group-name notif, teams-style badge, excel paste, spellcheck (0.1.11/batch64)
1. Message-info popup: ONE scroll region for the whole list with sticky Seen / Not-seen headers (was a separate scrollbar per section), realigned. 2. Group message notifications now title = GROUP name, body = "Sender: text". 3. Unread taskbar badge: solid red rounded-square (Teams-style) + white outline instead of the gradient disc. 4. Pasting Excel/Sheets cells no longer uploads a screenshot — when the clipboard carries real text, the text is pasted; image-only clipboards still upload. 5. Spell check in the message box (red squiggles) with a right-click menu of corrections + add-to-dictionary; spellcheck enabled on the textarea. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+33
-1
@@ -8,7 +8,7 @@
|
|||||||
// - external links open in the user's browser, not inside the app
|
// - external links open in the user's browser, not inside the app
|
||||||
//
|
//
|
||||||
// Server origin is configurable so the same build works against prod or a dev server.
|
// Server origin is configurable so the same build works against prod or a dev server.
|
||||||
const { app, BrowserWindow, session, desktopCapturer, shell, Menu, ipcMain, nativeImage, Notification } = require('electron');
|
const { app, BrowserWindow, session, desktopCapturer, shell, Menu, MenuItem, ipcMain, nativeImage, Notification } = require('electron');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
@@ -221,6 +221,28 @@ function createWindow() {
|
|||||||
if (!url.startsWith(SERVER_URL)) { shell.openExternal(url); return { action: 'deny' }; }
|
if (!url.startsWith(SERVER_URL)) { shell.openExternal(url); return { action: 'deny' }; }
|
||||||
return { action: 'allow' };
|
return { action: 'allow' };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Right-click menu: spelling corrections for the misspelled word under the cursor (+ add to
|
||||||
|
// dictionary), plus standard cut/copy/paste in editable fields. This is what makes the spell
|
||||||
|
// checker actionable — click a suggestion to fix the word in place.
|
||||||
|
win.webContents.on('context-menu', (_e, params) => {
|
||||||
|
const menu = new Menu();
|
||||||
|
for (const s of (params.dictionarySuggestions || [])) {
|
||||||
|
menu.append(new MenuItem({ label: s, click: () => win.webContents.replaceMisspelling(s) }));
|
||||||
|
}
|
||||||
|
if (params.misspelledWord) {
|
||||||
|
if (params.dictionarySuggestions && params.dictionarySuggestions.length) menu.append(new MenuItem({ type: 'separator' }));
|
||||||
|
menu.append(new MenuItem({ label: 'Add to dictionary', click: () => win.webContents.session.addWordToSpellCheckerDictionary(params.misspelledWord) }));
|
||||||
|
}
|
||||||
|
if (params.isEditable || params.editFlags.canCopy) {
|
||||||
|
if (menu.items.length) menu.append(new MenuItem({ type: 'separator' }));
|
||||||
|
if (params.editFlags.canCut) menu.append(new MenuItem({ role: 'cut' }));
|
||||||
|
if (params.editFlags.canCopy) menu.append(new MenuItem({ role: 'copy' }));
|
||||||
|
if (params.isEditable) menu.append(new MenuItem({ role: 'paste' }));
|
||||||
|
if (params.isEditable && params.editFlags.canSelectAll) menu.append(new MenuItem({ role: 'selectAll' }));
|
||||||
|
}
|
||||||
|
if (menu.items.length) menu.popup();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// The full Connect experience needs several web capabilities that Electron denies by
|
// The full Connect experience needs several web capabilities that Electron denies by
|
||||||
@@ -294,6 +316,16 @@ function configureSession() {
|
|||||||
ses.setPermissionRequestHandler((_wc, permission, callback) => callback(GRANTED.has(permission)));
|
ses.setPermissionRequestHandler((_wc, permission, callback) => callback(GRANTED.has(permission)));
|
||||||
// Sync check (some getUserMedia paths query this before requesting)
|
// Sync check (some getUserMedia paths query this before requesting)
|
||||||
ses.setPermissionCheckHandler((_wc, permission) => GRANTED.has(permission));
|
ses.setPermissionCheckHandler((_wc, permission) => GRANTED.has(permission));
|
||||||
|
// Spell check for the message box (red squiggles) with right-click corrections. Uses the OS
|
||||||
|
// dictionaries; en-US by default plus whatever the OS UI language is, so mixed typing still checks.
|
||||||
|
try {
|
||||||
|
ses.setSpellCheckerEnabled(true);
|
||||||
|
const langs = ['en-US'];
|
||||||
|
const sys = (app.getLocale && app.getLocale()) || '';
|
||||||
|
const avail = (ses.availableSpellCheckerLanguages || []);
|
||||||
|
if (sys && sys !== 'en-US' && (!avail.length || avail.includes(sys))) langs.push(sys);
|
||||||
|
ses.setSpellCheckerLanguages(langs);
|
||||||
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
app.whenReady().then(() => {
|
app.whenReady().then(() => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "biz-connect-desktop",
|
"name": "biz-connect-desktop",
|
||||||
"version": "0.1.10",
|
"version": "0.1.11",
|
||||||
"description": "Biz Connect technician desktop client — loads the Connect web UI with native screen capture",
|
"description": "Biz Connect technician desktop client — loads the Connect web UI with native screen capture",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "BizGaze",
|
"name": "BizGaze",
|
||||||
|
|||||||
+42
-22
@@ -553,16 +553,20 @@
|
|||||||
.modal input#grpName:focus,.modal input#giName:focus{outline:none;border-color:var(--brand);}
|
.modal input#grpName:focus,.modal input#giName:focus{outline:none;border-color:var(--brand);}
|
||||||
/* Branded confirm dialog (replaces the OS/Electron window.confirm). */
|
/* Branded confirm dialog (replaces the OS/Electron window.confirm). */
|
||||||
.bz-confirm .bzc-msg{margin:.2rem 0 1.1rem;color:var(--ink);font-size:.92rem;line-height:1.5;}
|
.bz-confirm .bzc-msg{margin:.2rem 0 1.1rem;color:var(--ink);font-size:.92rem;line-height:1.5;}
|
||||||
.seen-modal .seen-list{max-height:38vh;overflow-y:auto;display:flex;flex-direction:column;gap:.15rem;}
|
.seen-modal{max-width:360px;display:flex;flex-direction:column;max-height:80vh;}
|
||||||
.seen-modal .seen-row{display:flex;align-items:center;gap:.6rem;padding:.32rem .3rem;}
|
.seen-modal>h3{flex:0 0 auto;}
|
||||||
|
/* ONE scroll region for the whole list; the two section headers stick to the top as you scroll */
|
||||||
|
.seen-modal .seen-scroll{flex:1 1 auto;overflow-y:auto;margin:.2rem -.4rem 0;padding:0 .4rem;min-height:0;}
|
||||||
|
.seen-modal .seen-row{display:flex;align-items:center;gap:.6rem;padding:.34rem .2rem;}
|
||||||
/* dark lettering on the pastel avatar (white was invisible on the light background), unless a real DP covers it */
|
/* dark lettering on the pastel avatar (white was invisible on the light background), unless a real DP covers it */
|
||||||
.seen-modal .seen-row .mini-av{width:32px;height:32px;flex:0 0 auto;border-radius:50%;display:grid;place-items:center;color:#334155;font-weight:700;font-size:.74rem;overflow:hidden;position:relative;}
|
.seen-modal .seen-row .mini-av{width:34px;height:34px;flex:0 0 auto;border-radius:50%;display:grid;place-items:center;color:#334155;font-weight:700;font-size:.76rem;overflow:hidden;position:relative;}
|
||||||
.seen-modal .seen-row .mini-av .av-img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;}
|
.seen-modal .seen-row .mini-av .av-img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;}
|
||||||
.seen-modal .seen-row .sn{font-size:.92rem;color:var(--ink);}
|
.seen-modal .seen-row .sn{font-size:.92rem;color:var(--ink);}
|
||||||
.seen-modal .seen-sec{display:flex;align-items:center;gap:.4rem;font-size:.72rem;font-weight:700;letter-spacing:.02em;text-transform:uppercase;color:var(--blue);margin:.7rem .2rem .25rem;}
|
.seen-modal .seen-sec{position:sticky;top:0;z-index:1;background:var(--card,#fff);display:flex;align-items:center;gap:.4rem;font-size:.72rem;font-weight:700;letter-spacing:.02em;text-transform:uppercase;color:var(--blue);padding:.5rem .2rem .3rem;}
|
||||||
.seen-modal .seen-sec.dim{color:var(--muted);}
|
.seen-modal .seen-sec.dim{color:var(--muted);}
|
||||||
.seen-modal .seen-sec .dot{width:7px;height:7px;border-radius:50%;background:#cbd5e1;}
|
.seen-modal .seen-sec .dot{width:7px;height:7px;border-radius:50%;background:#cbd5e1;}
|
||||||
.seen-modal .seen-sec .dot.ok{background:#22c55e;}
|
.seen-modal .seen-sec .dot.ok{background:#22c55e;}
|
||||||
|
.seen-modal .bzc-actions{flex:0 0 auto;margin-top:.7rem;}
|
||||||
.bzc-actions{display:flex;justify-content:flex-end;gap:.5rem;}
|
.bzc-actions{display:flex;justify-content:flex-end;gap:.5rem;}
|
||||||
.bzc-actions button{border:none;border-radius:10px;padding:.55rem 1rem;font-size:.9rem;font-weight:600;cursor:pointer;font-family:inherit;}
|
.bzc-actions button{border:none;border-radius:10px;padding:.55rem 1rem;font-size:.9rem;font-weight:600;cursor:pointer;font-family:inherit;}
|
||||||
.bzc-cancel{background:var(--blue-soft);color:var(--blue);}
|
.bzc-cancel{background:var(--blue-soft);color:var(--blue);}
|
||||||
@@ -846,7 +850,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<script src="/icons.js?v=5"></script>
|
<script src="/icons.js?v=5"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@twemoji/api@15.1.0/dist/twemoji.min.js" crossorigin="anonymous"></script>
|
<script src="https://cdn.jsdelivr.net/npm/@twemoji/api@15.1.0/dist/twemoji.min.js" crossorigin="anonymous"></script>
|
||||||
<script>window.__BUILD='2026-07-10-batch63';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);
|
<script>window.__BUILD='2026-07-10-batch64';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);
|
||||||
// Render modern (Twemoji) emojis in place of the OS's flat ones. No-op if the CDN didn't load
|
// Render modern (Twemoji) emojis in place of the OS's flat ones. No-op if the CDN didn't load
|
||||||
// (emojis stay as plain Unicode). (#5)
|
// (emojis stay as plain Unicode). (#5)
|
||||||
function twemojify(el){ try{ if(el && window.twemoji) window.twemoji.parse(el, { folder:'svg', ext:'.svg' }); }catch(_){} }</script>
|
function twemojify(el){ try{ if(el && window.twemoji) window.twemoji.parse(el, { folder:'svg', ext:'.svg' }); }catch(_){} }</script>
|
||||||
@@ -1378,18 +1382,18 @@ function setDesktopBadge(count){
|
|||||||
if(!(window.bizConnectNative && window.bizConnectNative.setUnread)) return;
|
if(!(window.bizConnectNative && window.bizConnectNative.setUnread)) return;
|
||||||
let dataUrl=null;
|
let dataUrl=null;
|
||||||
if(count>0){
|
if(count>0){
|
||||||
// Draw at 2× for a crisp taskbar overlay. A vertical red→pink gradient pill with a white ring
|
// Teams-style overlay: a solid red rounded-square (squircle) with a bold white count and a thin
|
||||||
// and a soft drop shadow reads as a modern notification badge (was a flat red disc).
|
// white outline so it separates cleanly from the app icon on the taskbar. Drawn at 2× for crispness.
|
||||||
const s=64, c=document.createElement('canvas'); c.width=s; c.height=s; const g=c.getContext('2d');
|
const s=64, c=document.createElement('canvas'); c.width=s; c.height=s; const g=c.getContext('2d');
|
||||||
const label=count>99?'99+':String(count);
|
const label=count>99?'99+':String(count);
|
||||||
const cx=s/2, cy=s/2, r=s/2-4;
|
const pad=3, rad=17, x=pad, y=pad, w=s-pad*2, h=s-pad*2;
|
||||||
g.save(); g.shadowColor='rgba(0,0,0,.35)'; g.shadowBlur=5; g.shadowOffsetY=2;
|
const rrect=(rx,ry,rw,rh,r)=>{ g.beginPath(); g.moveTo(rx+r,ry); g.arcTo(rx+rw,ry,rx+rw,ry+rh,r); g.arcTo(rx+rw,ry+rh,rx,ry+rh,r); g.arcTo(rx,ry+rh,rx,ry,r); g.arcTo(rx,ry,rx+rw,ry,r); g.closePath(); };
|
||||||
const grad=g.createLinearGradient(0,cy-r,0,cy+r); grad.addColorStop(0,'#ff5b6e'); grad.addColorStop(1,'#e11d48');
|
g.save(); g.shadowColor='rgba(0,0,0,.28)'; g.shadowBlur=4; g.shadowOffsetY=1.5;
|
||||||
g.fillStyle=grad; g.beginPath(); g.arc(cx,cy,r,0,2*Math.PI); g.fill(); g.restore();
|
g.fillStyle='#e6394f'; rrect(x,y,w,h,rad); g.fill(); g.restore(); // solid notification red
|
||||||
g.lineWidth=3.5; g.strokeStyle='#fff'; g.beginPath(); g.arc(cx,cy,r,0,2*Math.PI); g.stroke(); // white ring pops on any taskbar
|
g.lineWidth=3; g.strokeStyle='#fff'; rrect(x,y,w,h,rad); g.stroke(); // crisp white outline
|
||||||
g.fillStyle='#fff'; g.textAlign='center'; g.textBaseline='middle';
|
g.fillStyle='#fff'; g.textAlign='center'; g.textBaseline='middle';
|
||||||
g.font='800 '+(label.length>2?26:(label.length>1?32:38))+'px system-ui,Segoe UI,sans-serif';
|
g.font='800 '+(label.length>2?26:(label.length>1?33:39))+'px system-ui,Segoe UI,sans-serif';
|
||||||
g.fillText(label, cx, cy+2);
|
g.fillText(label, s/2, s/2+2);
|
||||||
dataUrl=c.toDataURL('image/png');
|
dataUrl=c.toDataURL('image/png');
|
||||||
}
|
}
|
||||||
window.bizConnectNative.setUnread(count, dataUrl);
|
window.bizConnectNative.setUnread(count, dataUrl);
|
||||||
@@ -1481,7 +1485,7 @@ function convoShellHTML(it){
|
|||||||
+ '<div class="composer-row">'
|
+ '<div class="composer-row">'
|
||||||
+ '<button type="button" class="ic-btn" id="attachBtn" title="Attach a file">'+ic('paperclip',20)+'</button>'
|
+ '<button type="button" class="ic-btn" id="attachBtn" title="Attach a file">'+ic('paperclip',20)+'</button>'
|
||||||
+ '<button type="button" class="ic-btn" id="fmtBtn" title="Formatting">'+ic('type',20)+'</button>'
|
+ '<button type="button" class="ic-btn" id="fmtBtn" title="Formatting">'+ic('type',20)+'</button>'
|
||||||
+ '<textarea id="msgInput" placeholder="Type a message…" autocomplete="off" maxlength="4000" rows="1"></textarea>'
|
+ '<textarea id="msgInput" placeholder="Type a message…" autocomplete="off" spellcheck="true" autocapitalize="sentences" maxlength="4000" rows="1"></textarea>'
|
||||||
+ (isG?'<button type="button" class="ic-btn" id="pollBtn" title="Create a poll">'+ic('barChart',20)+'</button>':'')
|
+ (isG?'<button type="button" class="ic-btn" id="pollBtn" title="Create a poll">'+ic('barChart',20)+'</button>':'')
|
||||||
+ '<button type="button" class="ic-btn" id="emojiBtn" title="Emoji">'+ic('smile',20)+'</button>'
|
+ '<button type="button" class="ic-btn" id="emojiBtn" title="Emoji">'+ic('smile',20)+'</button>'
|
||||||
+ '<button type="submit" class="sendbtn" title="Send" aria-label="Send">'+ic('send',18)+'</button>'
|
+ '<button type="submit" class="sendbtn" title="Send" aria-label="Send">'+ic('send',18)+'</button>'
|
||||||
@@ -1624,7 +1628,13 @@ function renderAttachBar(){
|
|||||||
function hideAttach(){ pendingAttachs=[]; const bar=document.getElementById('attachBar'); if(bar){ bar.style.display='none'; bar.innerHTML=''; } const fi=document.getElementById('fileInput'); if(fi) fi.value=''; }
|
function hideAttach(){ pendingAttachs=[]; const bar=document.getElementById('attachBar'); if(bar){ bar.style.display='none'; bar.innerHTML=''; } const fi=document.getElementById('fileInput'); if(fi) fi.value=''; }
|
||||||
// Paste an image from the clipboard (e.g. a screenshot) straight into the composer.
|
// Paste an image from the clipboard (e.g. a screenshot) straight into the composer.
|
||||||
function onPaste(e){
|
function onPaste(e){
|
||||||
const items=(e.clipboardData&&e.clipboardData.items)||[];
|
const cd=e.clipboardData; if(!cd) return;
|
||||||
|
// Excel / Google Sheets put BOTH a bitmap AND the cell text on the clipboard. If there's real text,
|
||||||
|
// let the textarea paste it normally (don't upload the screenshot). Only upload the image when the
|
||||||
|
// clipboard is image-ONLY (a genuine screenshot / copied picture with no accompanying text).
|
||||||
|
const plain=(cd.getData&&cd.getData('text/plain'))||'';
|
||||||
|
if(plain&&plain.trim()) return; // → default paste of the text
|
||||||
|
const items=cd.items||[];
|
||||||
for(const it of items){
|
for(const it of items){
|
||||||
if(it.type&&it.type.indexOf('image')===0){
|
if(it.type&&it.type.indexOf('image')===0){
|
||||||
const blob=it.getAsFile();
|
const blob=it.getAsFile();
|
||||||
@@ -1719,12 +1729,15 @@ function showSeenByModal(msg){
|
|||||||
const chip=(name,avatar)=>'<span class="mini-av" style="background:'+avColor(name)+'">'+(avatar?('<img class="av-img" src="'+pEsc(avatar)+'" onerror="this.remove()">'):'')+pEsc(initials(name))+'</span>';
|
const chip=(name,avatar)=>'<span class="mini-av" style="background:'+avColor(name)+'">'+(avatar?('<img class="av-img" src="'+pEsc(avatar)+'" onerror="this.remove()">'):'')+pEsc(initials(name))+'</span>';
|
||||||
const rowsFromNames=(arr)=>arr.map(n=>'<div class="seen-row">'+chip(n, avByName[String(n).trim().toLowerCase()]||null)+'<span class="sn">'+pEsc(n)+'</span></div>').join('');
|
const rowsFromNames=(arr)=>arr.map(n=>'<div class="seen-row">'+chip(n, avByName[String(n).trim().toLowerCase()]||null)+'<span class="sn">'+pEsc(n)+'</span></div>').join('');
|
||||||
const rowsFromMembers=(arr)=>arr.map(m=>'<div class="seen-row">'+chip(m.name, m.avatar||null)+'<span class="sn">'+pEsc(m.name)+'</span></div>').join('');
|
const rowsFromMembers=(arr)=>arr.map(m=>'<div class="seen-row">'+chip(m.name, m.avatar||null)+'<span class="sn">'+pEsc(m.name)+'</span></div>').join('');
|
||||||
let body='';
|
let inner='';
|
||||||
if(seenNames.length) body+='<div class="seen-sec"><span class="dot ok"></span>Seen · '+seenNames.length+'</div><div class="seen-list">'+rowsFromNames(seenNames)+'</div>';
|
if(seenNames.length) inner+='<div class="seen-sec"><span class="dot ok"></span>Seen · '+seenNames.length+'</div>'+rowsFromNames(seenNames);
|
||||||
else body+='<div class="gi-noresult" style="padding:.6rem 0">No one has seen this yet.</div>';
|
else inner+='<div class="gi-noresult" style="padding:.6rem .2rem">No one has seen this yet.</div>';
|
||||||
if(convoIsGroup && notSeen.length) body+='<div class="seen-sec dim"><span class="dot"></span>Not seen yet · '+notSeen.length+'</div><div class="seen-list">'+rowsFromMembers(notSeen)+'</div>';
|
if(convoIsGroup && notSeen.length) inner+='<div class="seen-sec dim"><span class="dot"></span>Not seen yet · '+notSeen.length+'</div>'+rowsFromMembers(notSeen);
|
||||||
|
// ONE scroll container for the whole list (section headers stick as you scroll) — not a scrollbar
|
||||||
|
// per section, which split the popup confusingly.
|
||||||
|
const body='<div class="seen-scroll">'+inner+'</div>';
|
||||||
const ov=document.createElement('div'); ov.className='modal-ov'; ov.id='seenModal';
|
const ov=document.createElement('div'); ov.className='modal-ov'; ov.id='seenModal';
|
||||||
ov.innerHTML='<div class="modal seen-modal" style="max-width:340px"><h3>'+ic('checkCheck',16)+' Message info</h3>'+body+'<div class="bzc-actions" style="margin-top:.9rem"><button type="button" class="bzc-ok" id="seenClose">Close</button></div></div>';
|
ov.innerHTML='<div class="modal seen-modal"><h3>'+ic('checkCheck',16)+' Message info</h3>'+body+'<div class="bzc-actions"><button type="button" class="bzc-ok" id="seenClose">Close</button></div></div>';
|
||||||
document.body.appendChild(ov);
|
document.body.appendChild(ov);
|
||||||
ov.addEventListener('mousedown',e=>{ if(e.target===ov) ov.remove(); });
|
ov.addEventListener('mousedown',e=>{ if(e.target===ov) ov.remove(); });
|
||||||
ov.querySelector('#seenClose').onclick=()=>ov.remove();
|
ov.querySelector('#seenClose').onclick=()=>ov.remove();
|
||||||
@@ -2546,7 +2559,14 @@ function onChatMessage(m){
|
|||||||
// Popup rule: ping always. In-page popup only when the tab is VISIBLE but you're on another
|
// Popup rule: ping always. In-page popup only when the tab is VISIBLE but you're on another
|
||||||
// chat. When the tab is HIDDEN, let Web Push show it (the SW). If push isn't active, fall
|
// chat. When the tab is HIDDEN, let Web Push show it (the SW). If push isn't active, fall
|
||||||
// back to an in-page popup so hidden-tab users still get alerted.
|
// back to an in-page popup so hidden-tab users still get alerted.
|
||||||
if(notifOn(kind)){ playPing(); const wantPopup=!(isOpen && !document.hidden); if(wantPopup && !(document.hidden && pushActive)) notify((m.fromName||'New message'), m.body?(m.body.length>80?m.body.slice(0,80)+'…':m.body):'Sent an attachment', kind, rid); }
|
if(notifOn(kind)){ playPing(); const wantPopup=!(isOpen && !document.hidden);
|
||||||
|
if(wantPopup && !(document.hidden && pushActive)){
|
||||||
|
const prev=m.body?(m.body.length>80?m.body.slice(0,80)+'…':m.body):'Sent an attachment';
|
||||||
|
// Group: title = GROUP name, body = "Sender: message" (so you know which group it's from).
|
||||||
|
if(kind==='group') notify((it&&it.name)||m.groupName||'Group', (m.fromName?m.fromName+': ':'')+prev, kind, rid);
|
||||||
|
else notify((m.fromName||'New message'), prev, kind, rid);
|
||||||
|
}
|
||||||
|
}
|
||||||
// Activity-center entries for things easy to miss.
|
// Activity-center entries for things easy to miss.
|
||||||
if(m.poll) addNotif({icon:'barChart', text:pEsc(m.fromName||'Someone')+' created a poll'+(m.poll.question?': '+pEsc(m.poll.question):''), link:{kind, id:rid}});
|
if(m.poll) addNotif({icon:'barChart', text:pEsc(m.fromName||'Someone')+' created a poll'+(m.poll.question?': '+pEsc(m.poll.question):''), link:{kind, id:rid}});
|
||||||
else if(kind==='dm' && wasNew) addNotif({icon:'chat', text:'New chat from '+pEsc(m.fromName||'someone'), link:{kind:'dm', id:rid}});
|
else if(kind==='dm' && wasNew) addNotif({icon:'chat', text:'New chat from '+pEsc(m.fromName||'someone'), link:{kind:'dm', id:rid}});
|
||||||
|
|||||||
Reference in New Issue
Block a user