mobile: hide iOS keyboard accessory bar + composer-anchored attach menu (batch134)

- Accessory bar: call Keyboard.setAccessoryBarVisible({isVisible:false}) on iOS in the native
  IIFE, hiding the grey chevrons+Done strip above the keyboard. The plugin is already bundled
  in the current TestFlight build, so this takes effect on a web deploy — no rebuild.
- Attach: tapping the paperclip now opens a composer-anchored Photos/Camera/Document menu
  (like the emoji/mention popups) instead of firing the generic mid-screen OS chooser as the
  first thing. Each option opens a type-scoped picker (image/*, capture). Web fix, all clients.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-19 11:35:59 +05:30
parent a7f954ae44
commit 9f3e8b83c7
2 changed files with 22 additions and 5 deletions
+1 -1
View File
@@ -31,7 +31,7 @@ On the **Register an App ID** page, only three fields matter — leave everythin
1. [codemagic.io](https://codemagic.io) → sign in with the git provider → add this repository.
2. **Teams → Integrations → App Store Connect → Connect**, upload the `.p8`, paste the **Issuer ID** and
**Key ID**. **Name it exactly `BizGaze App Store Connect`** (the `codemagic.yaml` references that name).
3. Codemagic detects `codemagic.yaml`. That's all the signing setup — automatic signing creates the
3. Codemagic detects `codemagic.yaml`. That's all the signing setup — automatic signing creates the
distribution certificate + provisioning profile from this key on the first build.
## Step 4 — Run the build
+21 -4
View File
@@ -589,6 +589,12 @@
/* (message action button styling is under .msg-actions) */
.emoji-pop{position:absolute;bottom:64px;left:12px;width:330px;height:300px;background:#fff;border:1px solid var(--line);border-radius:12px;box-shadow:0 10px 28px rgba(0,0,0,.18);z-index:50;display:flex;flex-direction:column;overflow:hidden;}
.mention-pop{position:absolute;left:12px;right:12px;bottom:64px;max-height:240px;overflow:auto;background:var(--card);border:1px solid var(--line);border-radius:12px;box-shadow:0 10px 28px rgba(0,0,0,.18);z-index:60;padding:.3rem;}
/* Attach menu: anchored to the composer (like the emoji/mention popups) so options appear right above the
paperclip instead of the OS chooser jumping to mid-screen. */
.attach-pop{position:absolute;left:12px;bottom:64px;z-index:60;background:var(--card);border:1px solid var(--line);border-radius:12px;box-shadow:0 10px 28px rgba(0,0,0,.18);padding:.3rem;display:flex;flex-direction:column;min-width:190px;}
.attach-pop .attach-opt{display:flex;align-items:center;gap:.6rem;padding:.65rem .7rem;border:none;background:transparent;color:var(--ink);cursor:pointer;border-radius:8px;font-size:.95rem;text-align:left;width:100%;}
.attach-pop .attach-opt:hover{background:var(--blue-soft);color:var(--blue);}
.attach-pop .attach-opt svg{flex:0 0 auto;}
.mention-pop .mrow{display:flex;align-items:center;gap:.55rem;padding:.4rem .55rem;border-radius:8px;cursor:pointer;}
.mention-pop .mrow.sel{background:var(--blue-soft);}
.mention-pop .mn{font-weight:600;color:var(--ink);}
@@ -1152,7 +1158,7 @@
</head>
<body>
<script src="/icons.js?v=6"></script>
<script>window.__BUILD='2026-07-19-batch133';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);
<script>window.__BUILD='2026-07-19-batch134';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);
// Emoji are rendered with the OS's own (colour) emoji font — instant, zero network.
//
// We used to run Twemoji over every emoji, which swapped each one for an <img> pulled INDIVIDUALLY from
@@ -1193,6 +1199,7 @@ window.__perfProbe=function(label){ try{
function setKb(px){ px=Math.max(0,Math.round(px)); r.style.setProperty('--kb',px+'px'); document.body.classList.toggle('kb-open',px>4); var b=document.getElementById('msgs'); if(b && (b.scrollHeight-b.scrollTop-b.clientHeight)<400) b.scrollTop=b.scrollHeight; } /* #1: only keep pinned to newest when ALREADY near the bottom replying to an OLD message must not yank to the latest */
window.__bzResetKb=function(){ setKb(0); }; // called on composer blur as a safety net (#1: keyboard closed but lift stayed, leaving empty space after reply-cancel)
var KB=C.Plugins&&C.Plugins.Keyboard;
if(p==='ios' && KB && KB.setAccessoryBarVisible){ try{ KB.setAccessoryBarVisible({ isVisible:false }); }catch(_){} } // hide the grey iOS input accessory bar (up/down chevrons + Done) above the keyboard — iOS-only, no-op elsewhere
if(KB && KB.addListener){
KB.addListener('keyboardWillShow', function(info){ var raw=(info&&info.keyboardHeight)||0; var kb = raw>window.innerHeight ? raw/(window.devicePixelRatio||1) : raw; setKb(kb); });
KB.addListener('keyboardWillHide', function(){ setKb(0); });
@@ -1974,9 +1981,16 @@ function convoShellHTML(it){
+ '</div>'
+ '</div>'
+ '<input type="file" id="fileInput" multiple style="display:none">'
+ '<input type="file" id="fileInputImg" accept="image/*" multiple style="display:none">'
+ '<input type="file" id="fileInputCam" accept="image/*" capture="environment" style="display:none">'
+ '</form>'
+ '<div class="emoji-pop" id="emojiPop" style="display:none"></div>'
+ '<div class="mention-pop" id="mentionPop" style="display:none"></div>'
+ '<div class="attach-pop" id="attachPop" style="display:none">'
+ '<button type="button" class="attach-opt" data-src="photo">'+ic('image',18)+'<span>Photos</span></button>'
+ '<button type="button" class="attach-opt" data-src="camera">'+ic('camera',18)+'<span>Camera</span></button>'
+ '<button type="button" class="attach-opt" data-src="file">'+ic('paperclip',18)+'<span>Document</span></button>'
+ '</div>'
+ '</div>';
}
// Small round avatar (photo if the member has one, else colored initials) for a group message sender.
@@ -2205,7 +2219,7 @@ function renderAttachBar(){
pendingAttachs.splice(i,1); 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=''; } ['fileInput','fileInputImg','fileInputCam'].forEach(id=>{ const fi=document.getElementById(id); if(fi) fi.value=''; }); const ap=document.getElementById('attachPop'); if(ap) ap.style.display='none'; }
// Paste an image from the clipboard (e.g. a screenshot) straight into the composer.
function onPaste(e){
const cd=e.clipboardData; if(!cd) return;
@@ -2228,6 +2242,7 @@ document.addEventListener('click',(e)=>{
const pop=document.getElementById('emojiPop'), btn=document.getElementById('emojiBtn');
if(pop && !pop.contains(e.target) && !(btn&&btn.contains(e.target)) && !e.target.closest('.react-btn')) closeEmoji();
});
document.addEventListener('click',(e)=>{ const ap=document.getElementById('attachPop'); if(!ap||ap.style.display==='none') return; const abtn=document.getElementById('attachBtn'); if(!ap.contains(e.target) && !(abtn&&abtn.contains(e.target))) ap.style.display='none'; }); // close the attach menu on an outside tap
let emojiOpen=false;
function openEmoji(mode, anchorEl){ emojiMode=mode||'compose'; if(emojiMode&&emojiMode.react&&emojiCat==='gif') emojiCat=0; /* GIFs can't be a reaction */ const pop=document.getElementById('emojiPop'); if(!pop) return; emojiOpen=true; renderEmojiPop(pop); pop.style.display='flex'; positionEmojiPop(pop, anchorEl); }
// Anchor the picker at its trigger: above the composer emoji icon, or at the message you reacted to.
@@ -2881,8 +2896,10 @@ async function openConvo(kind,id){
const back=document.getElementById('convoBack'); if(back) back.onclick=showWelcome;
const form=document.getElementById('composer'); if(form) form.addEventListener('submit',(e)=>{ e.preventDefault(); sendMessage(); });
clearReply(); cancelEdit(); hideAttach();
const ab2=document.getElementById('attachBtn'); if(ab2) ab2.onclick=()=>{ const fi=document.getElementById('fileInput'); if(fi) fi.click(); };
const fi=document.getElementById('fileInput'); if(fi) fi.onchange=()=>{ Array.from(fi.files||[]).forEach(uploadFile); fi.value=''; }; // #5: queue every selected file
const ab2=document.getElementById('attachBtn'); const apop=document.getElementById('attachPop');
if(ab2&&apop) ab2.onclick=(e)=>{ e.stopPropagation(); apop.style.display = apop.style.display==='none' ? 'flex' : 'none'; }; // show a composer-anchored menu instead of firing the generic mid-screen OS chooser straight away
if(apop) apop.querySelectorAll('.attach-opt').forEach(b=>b.onclick=(e)=>{ e.stopPropagation(); apop.style.display='none'; const src=b.dataset.src; const inp = src==='photo'?document.getElementById('fileInputImg') : src==='camera'?document.getElementById('fileInputCam') : document.getElementById('fileInput'); if(inp) inp.click(); }); // Photos → gallery, Camera → capture, Document → any file
['fileInput','fileInputImg','fileInputCam'].forEach(fid=>{ const fi=document.getElementById(fid); if(fi) fi.onchange=()=>{ Array.from(fi.files||[]).forEach(uploadFile); fi.value=''; }; }); // #5: queue every selected file from whichever scoped input fired
const eb=document.getElementById('emojiBtn'); if(eb) eb.onclick=(e)=>{ e.stopPropagation(); emojiOpen?closeEmoji():openEmoji('compose', eb); };
const fb=document.getElementById('fmtBtn'); if(fb) fb.onclick=()=>{ const bar=document.getElementById('fmtBar'); if(bar) bar.style.display=bar.style.display==='none'?'flex':'none'; };
const fbar=document.getElementById('fmtBar'); if(fbar) fbar.querySelectorAll('button[data-fmt]').forEach(b=>b.onclick=()=>applyFmt(b.dataset.fmt));