Skip to content

Commit 54e4447

Browse files
authored
Merge pull request #507 from esokullu/main
Fix New conversation confirmation in Vivaldi
2 parents d28b4e7 + 764b7a2 commit 54e4447

51 files changed

Lines changed: 580 additions & 51 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/chrome/src/ui/sidepanel.html

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,42 @@ <h2 data-i18n="ob.tokens.title"></h2>
115115
</div>
116116
</div>
117117

118+
<div
119+
id="new-conversation-confirm"
120+
class="new-conversation-confirm hidden"
121+
role="dialog"
122+
aria-modal="true"
123+
aria-labelledby="new-conversation-confirm-title"
124+
tabindex="-1"
125+
>
126+
<div class="new-conversation-confirm-card">
127+
<div class="new-conversation-confirm-icon" aria-hidden="true">
128+
<svg viewBox="0 0 24 24">
129+
<path d="M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z"/>
130+
<path d="M12 9v4"/>
131+
<path d="M12 17h.01"/>
132+
</svg>
133+
</div>
134+
<h2 id="new-conversation-confirm-title" data-i18n="sp.clear.confirm">This conversation will be lost. Start a new conversation?</h2>
135+
<div class="new-conversation-confirm-actions">
136+
<button
137+
id="new-conversation-confirm-cancel"
138+
class="new-conversation-confirm-cancel"
139+
type="button"
140+
data-new-conversation-confirm-action
141+
data-i18n="sp.scheduled.cancel"
142+
>Cancel</button>
143+
<button
144+
id="new-conversation-confirm-accept"
145+
class="new-conversation-confirm-accept"
146+
type="button"
147+
data-new-conversation-confirm-action
148+
data-i18n="sp.btn.clear"
149+
>New conversation (clear history)</button>
150+
</div>
151+
</div>
152+
</div>
153+
118154
<div id="app">
119155
<!-- Header -->
120156
<header id="header">

src/chrome/src/ui/sidepanel.js

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,10 @@ const inputHighlightEl = document.getElementById('input-highlight');
505505
const sendBtn = document.getElementById('btn-send');
506506
const micBtn = document.getElementById('btn-mic');
507507
const clearBtn = document.getElementById('btn-clear');
508+
const appEl = document.getElementById('app');
509+
const newConversationConfirmEl = document.getElementById('new-conversation-confirm');
510+
const newConversationConfirmCancelBtn = document.getElementById('new-conversation-confirm-cancel');
511+
const newConversationConfirmAcceptBtn = document.getElementById('new-conversation-confirm-accept');
508512
const historyBtn = document.getElementById('btn-history');
509513
const settingsBtn = document.getElementById('btn-settings');
510514
const verboseBtn = document.getElementById('btn-verbose');
@@ -975,6 +979,7 @@ const awaitingPlanReviewTabs = new Set();
975979
const processingTabs = new Set();
976980
const abortRequestedTabs = new Set();
977981
const clearingConversationTabs = new Set();
982+
let newConversationConfirmationState = null;
978983
const localRunRequestIds = new Map();
979984
const localRunFollowers = new Map();
980985
const cancelledRunRecoveryRequestIds = new Set();
@@ -1021,6 +1026,86 @@ function isConversationClearInProgress(tabId = currentTabId) {
10211026
return Number.isFinite(numericTabId) && clearingConversationTabs.has(numericTabId);
10221027
}
10231028

1029+
function settleNewConversationConfirmation(confirmed, { restoreFocus = true } = {}) {
1030+
const state = newConversationConfirmationState;
1031+
if (!state) return;
1032+
newConversationConfirmationState = null;
1033+
newConversationConfirmEl?.classList.add('hidden');
1034+
if (appEl) appEl.inert = state.appWasInert;
1035+
if (restoreFocus && state.previouslyFocusedElement?.isConnected
1036+
&& typeof state.previouslyFocusedElement.focus === 'function') {
1037+
state.previouslyFocusedElement.focus({ preventScroll: true });
1038+
}
1039+
state.resolve(!!confirmed);
1040+
}
1041+
1042+
function requestNewConversationConfirmation(tabId) {
1043+
if (newConversationConfirmationState || !appEl || !newConversationConfirmEl
1044+
|| !newConversationConfirmCancelBtn || !newConversationConfirmAcceptBtn) {
1045+
return Promise.resolve(false);
1046+
}
1047+
1048+
let resolveConfirmation;
1049+
const promise = new Promise((resolve) => {
1050+
resolveConfirmation = resolve;
1051+
});
1052+
newConversationConfirmationState = {
1053+
tabId: Number(tabId),
1054+
promise,
1055+
resolve: resolveConfirmation,
1056+
appWasInert: appEl.inert,
1057+
previouslyFocusedElement: document.activeElement,
1058+
};
1059+
1060+
applyDOMTranslations(newConversationConfirmEl);
1061+
appEl.inert = true;
1062+
newConversationConfirmEl.classList.remove('hidden');
1063+
newConversationConfirmCancelBtn.focus({ preventScroll: true });
1064+
return promise;
1065+
}
1066+
1067+
function handleNewConversationConfirmKeydown(event) {
1068+
if (!newConversationConfirmationState) return false;
1069+
1070+
if (event.key === 'Escape') {
1071+
event.preventDefault();
1072+
settleNewConversationConfirmation(false);
1073+
} else if (event.key === 'Tab') {
1074+
const focusable = Array.from(newConversationConfirmEl.querySelectorAll(
1075+
'[data-new-conversation-confirm-action]:not([disabled])',
1076+
));
1077+
if (!focusable.length) {
1078+
event.preventDefault();
1079+
newConversationConfirmEl.focus({ preventScroll: true });
1080+
} else {
1081+
const first = focusable[0];
1082+
const last = focusable[focusable.length - 1];
1083+
const activeElement = document.activeElement;
1084+
if (event.shiftKey && (activeElement === first || !newConversationConfirmEl.contains(activeElement))) {
1085+
event.preventDefault();
1086+
last.focus({ preventScroll: true });
1087+
} else if (!event.shiftKey && (activeElement === last || !newConversationConfirmEl.contains(activeElement))) {
1088+
event.preventDefault();
1089+
first.focus({ preventScroll: true });
1090+
}
1091+
}
1092+
}
1093+
1094+
event.stopImmediatePropagation();
1095+
return true;
1096+
}
1097+
1098+
document.addEventListener('keydown', handleNewConversationConfirmKeydown, true);
1099+
newConversationConfirmCancelBtn?.addEventListener('click', () => {
1100+
settleNewConversationConfirmation(false);
1101+
});
1102+
newConversationConfirmAcceptBtn?.addEventListener('click', () => {
1103+
settleNewConversationConfirmation(true);
1104+
});
1105+
newConversationConfirmEl?.addEventListener('click', (event) => {
1106+
if (event.target === newConversationConfirmEl) settleNewConversationConfirmation(false);
1107+
});
1108+
10241109
function setTabAbortRequested(tabId, requested) {
10251110
const numericTabId = Number(tabId);
10261111
if (!Number.isFinite(numericTabId)) return;
@@ -3542,6 +3627,10 @@ if (verboseBtn) {
35423627

35433628
async function switchToTab(newTabId) {
35443629
if (newTabId === currentTabId && renderedTabId === newTabId) { return; }
3630+
if (newConversationConfirmationState
3631+
&& !sameTabId(newConversationConfirmationState.tabId, newTabId)) {
3632+
settleNewConversationConfirmation(false, { restoreFocus: false });
3633+
}
35453634
const switchGeneration = ++tabSwitchGeneration;
35463635
tabSwitchTransitionId = newTabId;
35473636
queuedTabSwitchMessages = [];
@@ -10291,8 +10380,9 @@ document.addEventListener('wb-locale-changed', () => {
1029110380

1029210381
clearBtn.addEventListener('click', async () => {
1029310382
const tabId = currentTabId;
10294-
if (isConversationClearInProgress(tabId)) return;
10295-
if (!window.confirm(t('sp.clear.confirm'))) return;
10383+
if (isConversationClearInProgress(tabId) || newConversationConfirmationState) return;
10384+
if (!await requestNewConversationConfirmation(tabId)) return;
10385+
if (!sameTabId(currentTabId, tabId)) return;
1029610386
setConversationClearInProgress(tabId, true);
1029710387
try {
1029810388
suppressRunUpdatesForClearedConversation(tabId);

src/chrome/styles/sidepanel.css

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,128 @@ body {
120120
display: none;
121121
}
122122

123+
.new-conversation-confirm {
124+
position: fixed;
125+
inset: 0;
126+
z-index: 1050;
127+
display: flex;
128+
align-items: center;
129+
justify-content: center;
130+
padding: 18px;
131+
background: var(--overlay-bg-strong);
132+
backdrop-filter: blur(3px);
133+
animation: fadeIn 0.16s ease;
134+
}
135+
136+
.new-conversation-confirm.hidden {
137+
display: none;
138+
}
139+
140+
.new-conversation-confirm-card {
141+
display: grid;
142+
grid-template-columns: 36px minmax(0, 1fr);
143+
gap: 14px 12px;
144+
align-items: center;
145+
width: min(100%, 330px);
146+
padding: 20px;
147+
border: 1px solid color-mix(in srgb, var(--error) 34%, var(--border));
148+
border-radius: 14px;
149+
background: var(--bg-secondary);
150+
box-shadow: 0 18px 48px rgba(0, 0, 0, 0.30);
151+
}
152+
153+
.new-conversation-confirm-icon {
154+
display: grid;
155+
place-items: center;
156+
width: 36px;
157+
height: 36px;
158+
border-radius: 10px;
159+
background: color-mix(in srgb, var(--error) 14%, transparent);
160+
color: var(--error-soft);
161+
}
162+
163+
.new-conversation-confirm-icon svg {
164+
width: 20px;
165+
height: 20px;
166+
fill: none;
167+
stroke: currentColor;
168+
stroke-width: 1.8;
169+
stroke-linecap: round;
170+
stroke-linejoin: round;
171+
}
172+
173+
.new-conversation-confirm-card h2 {
174+
color: var(--text-primary);
175+
font-size: 15px;
176+
font-weight: 700;
177+
line-height: 1.4;
178+
}
179+
180+
.new-conversation-confirm-actions {
181+
grid-column: 1 / -1;
182+
display: grid;
183+
grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.4fr);
184+
gap: 8px;
185+
}
186+
187+
.new-conversation-confirm-actions button {
188+
min-height: 38px;
189+
padding: 8px 10px;
190+
border: 1px solid transparent;
191+
border-radius: 8px;
192+
cursor: pointer;
193+
font: inherit;
194+
font-size: 12px;
195+
font-weight: 700;
196+
line-height: 1.25;
197+
transition: background 0.15s, border-color 0.15s, transform 0.1s;
198+
}
199+
200+
.new-conversation-confirm-actions button:active {
201+
transform: scale(0.98);
202+
}
203+
204+
.new-conversation-confirm-actions button:focus-visible {
205+
outline: 3px solid color-mix(in srgb, var(--accent) 42%, transparent);
206+
outline-offset: 2px;
207+
}
208+
209+
.new-conversation-confirm-actions .new-conversation-confirm-cancel {
210+
border-color: var(--border);
211+
background: var(--bg-input);
212+
color: var(--text-secondary);
213+
}
214+
215+
.new-conversation-confirm-actions .new-conversation-confirm-cancel:hover {
216+
border-color: var(--text-muted);
217+
color: var(--text-primary);
218+
}
219+
220+
.new-conversation-confirm-actions .new-conversation-confirm-accept {
221+
background: var(--error);
222+
color: #fff;
223+
}
224+
225+
.new-conversation-confirm-actions .new-conversation-confirm-accept:hover {
226+
background: color-mix(in srgb, var(--error) 86%, #000);
227+
}
228+
229+
@media (max-width: 340px) {
230+
.new-conversation-confirm-actions {
231+
grid-template-columns: 1fr;
232+
}
233+
}
234+
235+
@media (prefers-reduced-motion: reduce) {
236+
.new-conversation-confirm {
237+
animation: none;
238+
}
239+
240+
.new-conversation-confirm-actions button {
241+
transition: none;
242+
}
243+
}
244+
123245
.pin-coachmark {
124246
position: fixed;
125247
inset: 0;

src/firefox/src/ui/sidepanel.html

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,42 @@ <h2 data-i18n="ob.tokens.title"></h2>
8989
</div>
9090
</div>
9191

92+
<div
93+
id="new-conversation-confirm"
94+
class="new-conversation-confirm hidden"
95+
role="dialog"
96+
aria-modal="true"
97+
aria-labelledby="new-conversation-confirm-title"
98+
tabindex="-1"
99+
>
100+
<div class="new-conversation-confirm-card">
101+
<div class="new-conversation-confirm-icon" aria-hidden="true">
102+
<svg viewBox="0 0 24 24">
103+
<path d="M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z"/>
104+
<path d="M12 9v4"/>
105+
<path d="M12 17h.01"/>
106+
</svg>
107+
</div>
108+
<h2 id="new-conversation-confirm-title" data-i18n="sp.clear.confirm">This conversation will be lost. Start a new conversation?</h2>
109+
<div class="new-conversation-confirm-actions">
110+
<button
111+
id="new-conversation-confirm-cancel"
112+
class="new-conversation-confirm-cancel"
113+
type="button"
114+
data-new-conversation-confirm-action
115+
data-i18n="sp.scheduled.cancel"
116+
>Cancel</button>
117+
<button
118+
id="new-conversation-confirm-accept"
119+
class="new-conversation-confirm-accept"
120+
type="button"
121+
data-new-conversation-confirm-action
122+
data-i18n="sp.btn.clear"
123+
>New conversation (clear history)</button>
124+
</div>
125+
</div>
126+
</div>
127+
92128
<div id="app">
93129
<!-- Header -->
94130
<header id="header">

0 commit comments

Comments
 (0)