ext(affordances): wireFillFromTab + .glyph-btn CSS
This commit is contained in:
@@ -10,6 +10,7 @@
|
|||||||
--bg-page: #0d1117;
|
--bg-page: #0d1117;
|
||||||
--bg-pane: #161b22;
|
--bg-pane: #161b22;
|
||||||
--bg-elevated: #21262d;
|
--bg-elevated: #21262d;
|
||||||
|
--bg-input: #161b22;
|
||||||
--border-subtle: #30363d;
|
--border-subtle: #30363d;
|
||||||
|
|
||||||
/* Text */
|
/* Text */
|
||||||
@@ -1332,3 +1333,32 @@ textarea {
|
|||||||
padding: 1px 5px;
|
padding: 1px 5px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Glyph button used by smart-input affordances. Sits inline with an input. */
|
||||||
|
.glyph-btn {
|
||||||
|
min-width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
padding: 0 6px;
|
||||||
|
background: var(--bg-input);
|
||||||
|
border: 1px solid var(--border-subtle);
|
||||||
|
border-radius: 3px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.glyph-btn:hover:not(:disabled) {
|
||||||
|
border-color: var(--accent);
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
.glyph-btn:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: var(--focus-ring);
|
||||||
|
}
|
||||||
|
.glyph-btn:disabled {
|
||||||
|
opacity: 0.4;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
|
import { wireFillFromTab } from '../url-tools';
|
||||||
|
|
||||||
|
describe('wireFillFromTab', () => {
|
||||||
|
let form: HTMLElement;
|
||||||
|
let sendMessage: ReturnType<typeof vi.fn>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
form = document.createElement('div');
|
||||||
|
form.innerHTML = `
|
||||||
|
<input id="f-title" type="text" />
|
||||||
|
<div class="inline-row">
|
||||||
|
<input id="f-url" type="text" />
|
||||||
|
<button id="fill-from-tab-btn" class="glyph-btn" type="button" title="fill from active tab">⤓</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
document.body.appendChild(form);
|
||||||
|
sendMessage = vi.fn();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fills url + title from active tab on click', async () => {
|
||||||
|
sendMessage.mockResolvedValue({ ok: true, data: { url: 'https://github.com/login', title: 'GitHub' } });
|
||||||
|
wireFillFromTab(form, { sendMessage });
|
||||||
|
(form.querySelector('#fill-from-tab-btn') as HTMLButtonElement).click();
|
||||||
|
await Promise.resolve(); await Promise.resolve();
|
||||||
|
expect((form.querySelector('#f-url') as HTMLInputElement).value).toBe('https://github.com/login');
|
||||||
|
expect((form.querySelector('#f-title') as HTMLInputElement).value).toBe('GitHub');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not overwrite a non-empty title', async () => {
|
||||||
|
(form.querySelector('#f-title') as HTMLInputElement).value = 'My GitHub';
|
||||||
|
sendMessage.mockResolvedValue({ ok: true, data: { url: 'https://github.com/login', title: 'GitHub' } });
|
||||||
|
wireFillFromTab(form, { sendMessage });
|
||||||
|
(form.querySelector('#fill-from-tab-btn') as HTMLButtonElement).click();
|
||||||
|
await Promise.resolve(); await Promise.resolve();
|
||||||
|
expect((form.querySelector('#f-title') as HTMLInputElement).value).toBe('My GitHub');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('disables the button if SW returns null', async () => {
|
||||||
|
sendMessage.mockResolvedValue({ ok: true, data: null });
|
||||||
|
wireFillFromTab(form, { sendMessage });
|
||||||
|
(form.querySelector('#fill-from-tab-btn') as HTMLButtonElement).click();
|
||||||
|
await Promise.resolve(); await Promise.resolve();
|
||||||
|
expect((form.querySelector('#fill-from-tab-btn') as HTMLButtonElement).disabled).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
24
extension/src/shared/form-affordances/url-tools.ts
Normal file
24
extension/src/shared/form-affordances/url-tools.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { GLYPH_FILL_FROM_TAB } from '../glyphs';
|
||||||
|
|
||||||
|
export interface FillFromTabOpts {
|
||||||
|
sendMessage: (msg: { type: 'get_active_tab_url' }) => Promise<{ ok: boolean; data?: { url: string; title: string } | null }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function wireFillFromTab(form: HTMLElement, opts: FillFromTabOpts): void {
|
||||||
|
const btn = form.querySelector<HTMLButtonElement>('#fill-from-tab-btn');
|
||||||
|
if (!btn) return;
|
||||||
|
btn.addEventListener('click', async () => {
|
||||||
|
const resp = await opts.sendMessage({ type: 'get_active_tab_url' });
|
||||||
|
if (!resp.ok || !resp.data) {
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.title = 'no active tab';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const urlEl = form.querySelector<HTMLInputElement>('#f-url');
|
||||||
|
const titleEl = form.querySelector<HTMLInputElement>('#f-title');
|
||||||
|
if (urlEl) urlEl.value = resp.data.url;
|
||||||
|
if (titleEl && !titleEl.value.trim()) titleEl.value = resp.data.title;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FILL_FROM_TAB_BTN_HTML = `<button id="fill-from-tab-btn" class="glyph-btn" type="button" title="fill from active tab">${GLYPH_FILL_FROM_TAB}</button>`;
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
--bg-page: #0d1117;
|
--bg-page: #0d1117;
|
||||||
--bg-pane: #161b22;
|
--bg-pane: #161b22;
|
||||||
--bg-elevated: #21262d;
|
--bg-elevated: #21262d;
|
||||||
|
--bg-input: #161b22;
|
||||||
--border-subtle: #30363d;
|
--border-subtle: #30363d;
|
||||||
|
|
||||||
/* Text */
|
/* Text */
|
||||||
@@ -1362,3 +1363,32 @@ textarea {
|
|||||||
.vault-lock-screen__form input {
|
.vault-lock-screen__form input {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Glyph button used by smart-input affordances. Sits inline with an input. */
|
||||||
|
.glyph-btn {
|
||||||
|
min-width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
padding: 0 6px;
|
||||||
|
background: var(--bg-input);
|
||||||
|
border: 1px solid var(--border-subtle);
|
||||||
|
border-radius: 3px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.glyph-btn:hover:not(:disabled) {
|
||||||
|
border-color: var(--accent);
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
.glyph-btn:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: var(--focus-ring);
|
||||||
|
}
|
||||||
|
.glyph-btn:disabled {
|
||||||
|
opacity: 0.4;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user