Dictate replies from mobile Search preview #954
|
|
@ -644,6 +644,8 @@ textarea { resize: vertical; min-height: 120px; }
|
|||
.search-preview-reply textarea { box-sizing:border-box; width:100%; min-height:110px; resize:vertical; scroll-margin-bottom:calc(190px + env(safe-area-inset-bottom)); }
|
||||
.search-preview-reply-actions { display:grid; grid-template-columns:1fr 1fr; gap:8px; }
|
||||
.search-preview-reply-actions button { min-height:44px; }
|
||||
.search-preview-reply .voice-conversation-controls button,
|
||||
.search-preview-reply .voice-conversation-review-actions button { min-height:44px; }
|
||||
.search-preview-navigation { display:grid; grid-template-columns:minmax(0,1fr) auto minmax(0,1fr); align-items:center; gap:8px; }
|
||||
.search-preview-actions { position:sticky; bottom:0; display:grid; gap:8px; padding:10px 0; padding-bottom:calc(10px + env(safe-area-inset-bottom)); background:#0b1526; }
|
||||
.search-preview-actions button, .search-preview-actions a { min-height:44px; box-sizing:border-box; display:flex; align-items:center; justify-content:center; }
|
||||
|
|
|
|||
|
|
@ -640,9 +640,13 @@
|
|||
const issueVoiceReply = mountConversationVoice('issue-comment', '#issue-comment');
|
||||
const pullVoiceReply = mountConversationVoice('pull-comment', '#pull-comment');
|
||||
const updateVoiceReply = mountConversationVoice('update-reply', '#update-reply');
|
||||
const searchVoiceReply = mountConversationVoice('search-reply', '#search-preview-reply');
|
||||
function conversationVoiceTarget(kind, item) {
|
||||
return kind + ':' + String(item?.repository || '').trim().toLowerCase() + '#' + Number(item?.number);
|
||||
}
|
||||
function searchConversationVoiceTarget(item) {
|
||||
return 'search:' + conversationVoiceTarget(item?.kind, item);
|
||||
}
|
||||
const createIssueAttachmentController = issueAttachment.mount({
|
||||
input: qs('#create-issue-attachment'),
|
||||
inputs: [qs('#take-create-issue-photo'), qs('#create-issue-attachment')],
|
||||
|
|
@ -4945,6 +4949,7 @@
|
|||
qs('#close-search-preview').textContent = searchPreviewReturnKind === 'today-readiness'
|
||||
? 'Back to blockers' : 'Back to search';
|
||||
if (state.status === 'closed') {
|
||||
searchVoiceReply.cancel();
|
||||
sheet.classList.remove('open');
|
||||
return;
|
||||
}
|
||||
|
|
@ -4960,6 +4965,7 @@
|
|||
renderSearchPreviewReply(state, null, searchPreview, document);
|
||||
|
||||
if (state.status === 'loading') {
|
||||
searchVoiceReply.open(searchConversationVoiceTarget(state.item));
|
||||
searchPreviewDetail = null;
|
||||
qs('#search-preview-key').textContent = state.item.repository + ' #' + state.item.number;
|
||||
qs('#search-preview-title').textContent = state.item.title || 'Work preview';
|
||||
|
|
|
|||
|
|
@ -546,6 +546,22 @@
|
|||
<section class="search-preview-reply" aria-labelledby="search-preview-reply-title" hidden>
|
||||
<h2 id="search-preview-reply-title">Reply</h2>
|
||||
<textarea id="search-preview-reply" maxlength="10000" placeholder="Write a reply"></textarea>
|
||||
<section class="voice-conversation" id="voice-search-reply" aria-label="Dictate search result reply" hidden>
|
||||
<div class="voice-conversation-controls">
|
||||
<button id="start-voice-search-reply" type="button">Dictate reply</button>
|
||||
<button id="stop-voice-search-reply" type="button" hidden>Stop listening</button>
|
||||
</div>
|
||||
<div id="voice-search-reply-status" class="small" aria-live="polite"></div>
|
||||
<div class="voice-conversation-review" id="voice-search-reply-review" hidden>
|
||||
<label for="voice-search-reply-transcript">Review transcript</label>
|
||||
<textarea id="voice-search-reply-transcript" maxlength="10000"></textarea>
|
||||
<div class="voice-conversation-review-actions">
|
||||
<button id="append-voice-search-reply" type="button">Use transcript</button>
|
||||
<button id="replace-with-voice-search-reply" type="button" hidden>Replace draft</button>
|
||||
<button id="discard-voice-search-reply" type="button">Discard</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<div id="search-preview-reply-status" class="small" aria-live="polite"></div>
|
||||
<div class="search-preview-reply-actions">
|
||||
<button id="send-search-preview-reply" type="button">Send</button>
|
||||
|
|
|
|||
|
|
@ -185,3 +185,29 @@ def test_all_mobile_conversation_composers_ship_accessible_voice_review_controls
|
|||
assert "issueVoiceReply.cancel();" in dashboard
|
||||
assert "pullVoiceReply.cancel();" in dashboard
|
||||
assert "updateVoiceReply.cancel();" in dashboard
|
||||
|
||||
|
||||
def test_mobile_search_reply_ships_target_scoped_voice_review_and_cancels_on_close():
|
||||
html = (FRONTEND / "index.html").read_text()
|
||||
css = (FRONTEND / "dashboard.css").read_text()
|
||||
dashboard = (FRONTEND / "dashboard.js").read_text()
|
||||
|
||||
for control in (
|
||||
"voice-search-reply",
|
||||
"start-voice-search-reply",
|
||||
"stop-voice-search-reply",
|
||||
"voice-search-reply-review",
|
||||
"voice-search-reply-transcript",
|
||||
"append-voice-search-reply",
|
||||
"replace-with-voice-search-reply",
|
||||
"discard-voice-search-reply",
|
||||
"voice-search-reply-status",
|
||||
):
|
||||
assert f'id="{control}"' in html
|
||||
|
||||
assert 'for="voice-search-reply-transcript"' in html
|
||||
assert "mountConversationVoice('search-reply', '#search-preview-reply')" in dashboard
|
||||
assert "searchVoiceReply.open(searchConversationVoiceTarget(state.item))" in dashboard
|
||||
assert "searchVoiceReply.cancel();" in dashboard
|
||||
assert ".search-preview-reply .voice-conversation-controls button" in css
|
||||
assert "min-height:44px" in css
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user