Skip to content

Commit d54f95e

Browse files
committed
Implement copying password to clipboard
1 parent a363d76 commit d54f95e

File tree

6 files changed

+113
-6
lines changed

6 files changed

+113
-6
lines changed

meta/manifest.json

+8-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,14 @@
1515
"32": "./images/PasswordsExtensionIcon_32.png",
1616
"128": "./images/PasswordsExtensionIcon_128.png"
1717
},
18-
"permissions": ["nativeMessaging", "activeTab", "scripting"],
18+
"permissions": [
19+
"nativeMessaging",
20+
"activeTab",
21+
"scripting",
22+
"clipboardWrite"
23+
],
24+
"optional_permissions": ["tabs"],
25+
"host_permissions": ["*://*/*"],
1926
"background": {
2027
"scripts": ["./background.js"]
2128
},

src/background.ts

+14-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ browser.runtime.onMessage.addListener(async (message, sender) => {
3939
message.loginName,
4040
);
4141

42-
case "AUTO_FILL_PASSWORD":
42+
case "AUTO_FILL_PASSWORD": {
4343
const { username, password } = await getAPI().getPasswordForLoginName(
4444
message.tabId,
4545
message.url,
@@ -73,6 +73,19 @@ browser.runtime.onMessage.addListener(async (message, sender) => {
7373
if (errors.length !== 0) throw errors.length === 1 ? errors[0] : errors;
7474

7575
return true;
76+
}
77+
78+
case "COPY_PASSWORD": {
79+
const { password } = await getAPI().getPasswordForLoginName(
80+
message.tabId,
81+
message.url,
82+
message.loginName,
83+
);
84+
85+
await navigator.clipboard.writeText(password);
86+
87+
return true;
88+
}
7689
}
7790
} catch (e) {
7891
console.error(e);

src/popup/icons/copy.tsx

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
interface Props {
2+
title?: string;
3+
desc?: string;
4+
}
5+
6+
export function CopyIcon({
7+
title,
8+
desc,
9+
...props
10+
}: Props & React.SVGAttributes<SVGSVGElement>) {
11+
const uuid = crypto.randomUUID().replace(/-/g, "");
12+
13+
return (
14+
<svg
15+
xmlns="http://www.w3.org/2000/svg"
16+
width="16"
17+
height="16"
18+
viewBox="0 0 16 16"
19+
fill="none"
20+
stroke="currentColor"
21+
aria-labelledby={`${uuid}_title ${uuid}_desc`}
22+
role="img"
23+
{...props}
24+
>
25+
<title id={uuid}>{title}</title>
26+
<desc id={uuid}>{desc}</desc>
27+
<path
28+
d="M2.41667 6.33333V13C2.41667 13.598 2.90201 14.0833 3.50001 14.0833H8.83334C9.43134 14.0833 9.91667 13.598 9.91667 13V6.33333C9.91667 5.73533 9.43134 5.25 8.83334 5.25H3.50001C2.90201 5.25 2.41667 5.73533 2.41667 6.33333Z"
29+
stroke-width="1.5"
30+
stroke-miterlimit="10"
31+
stroke-linejoin="round"
32+
/>
33+
<path
34+
d="M11.5 11.4167C12.098 11.4167 12.5833 10.9313 12.5833 10.3333V3.66667C12.5833 3.06867 12.098 2.58333 11.5 2.58333H6.16666C5.56866 2.58333 5.08333 3.06867 5.08333 3.66667"
35+
stroke-width="1.5"
36+
stroke-miterlimit="10"
37+
stroke-linejoin="round"
38+
/>
39+
</svg>
40+
);
41+
}

src/popup/icons/key.tsx

+17-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,28 @@
1-
export function KeyIcon() {
1+
interface Props {
2+
title?: string;
3+
desc?: string;
4+
}
5+
6+
export function KeyIcon({
7+
title,
8+
desc,
9+
...props
10+
}: Props & React.SVGAttributes<SVGSVGElement>) {
11+
const uuid = crypto.randomUUID().replace(/-/g, "");
12+
213
return (
314
<svg
415
xmlns="http://www.w3.org/2000/svg"
516
width="12"
617
height="23"
718
viewBox="0 0 12 23"
819
fill="currentColor"
20+
aria-labelledby={`${uuid}_title ${uuid}_desc`}
21+
role="img"
22+
{...props}
923
>
24+
<title id={uuid}>{title}</title>
25+
<desc id={uuid}>{desc}</desc>
1026
<path d="M5.98632812,22.9248047 C6.13606771,22.9345703 6.26953125,22.8808594 6.38671875,22.7636719 L9.296875,19.8535156 C9.4140625,19.7298177 9.47265625,19.593099 9.47265625,19.4433594 C9.47265625,19.2936198 9.4140625,19.1634115 9.296875,19.0527344 L7.56835938,17.3339844 L9.98046875,14.921875 C10.0846354,14.8177083 10.1367188,14.6907552 10.1367188,14.5410156 C10.1367188,14.391276 10.0748698,14.2545573 9.95117188,14.1308594 L7.59765625,11.7578125 C8.99739583,11.1783854 10.0732422,10.3841146 10.8251953,9.375 C11.5771484,8.36588542 11.953125,7.22981771 11.953125,5.96679688 C11.953125,5.14648438 11.8001302,4.375 11.4941406,3.65234375 C11.188151,2.9296875 10.7617188,2.29492188 10.2148438,1.74804688 C9.66796875,1.20117188 9.03320312,0.773111979 8.31054688,0.463867188 C7.58789062,0.154622396 6.80989583,0 5.9765625,0 C5.14322917,0 4.36523438,0.154622396 3.64257813,0.463867188 C2.91992188,0.773111979 2.28515625,1.20117188 1.73828125,1.74804688 C1.19140625,2.29492188 0.764973958,2.9280599 0.458984375,3.64746094 C0.152994792,4.36686198 0,5.13997396 0,5.96679688 C0,6.79361979 0.154622396,7.57486979 0.463867188,8.31054688 C0.773111979,9.04622396 1.2109375,9.69238281 1.77734375,10.2490234 C2.34375,10.8056641 3.01106771,11.233724 3.77929688,11.5332031 L3.77929688,20.5664063 C3.77929688,20.6901042 3.80045573,20.8056641 3.84277344,20.9130859 C3.88509115,21.0205078 3.95507812,21.1230469 4.05273438,21.2207031 L5.625,22.7734375 C5.71614583,22.8645833 5.83658854,22.9150391 5.98632812,22.9248047 Z M5.9765625,5.625 C5.53385417,5.625 5.15136719,5.4671224 4.82910156,5.15136719 C4.50683594,4.83561198 4.34570312,4.44986979 4.34570312,3.99414062 C4.34570312,3.54492188 4.50520833,3.16080729 4.82421875,2.84179688 C5.14322917,2.52278646 5.52734375,2.36328125 5.9765625,2.36328125 C6.43229167,2.36328125 6.81966146,2.52441406 7.13867188,2.84667969 C7.45768229,3.16894531 7.6171875,3.55143229 7.6171875,3.99414062 C7.6171875,4.44986979 7.45768229,4.83561198 7.13867188,5.15136719 C6.81966146,5.4671224 6.43229167,5.625 5.9765625,5.625 Z" />
1127
</svg>
1228
);

src/popup/passwords.module.scss

+17
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,23 @@
7979
}
8080
}
8181

82+
> a {
83+
display: flex;
84+
padding: 4px 3px;
85+
border-radius: 5px;
86+
87+
> svg {
88+
color: $color-text-primary;
89+
height: 16px;
90+
width: 16px;
91+
}
92+
93+
&:hover {
94+
background-color: $color-background-3;
95+
cursor: pointer;
96+
}
97+
}
98+
8299
&:hover {
83100
background-color: $color-background-2;
84101
cursor: pointer;

src/popup/passwords.tsx

+16-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useCurrentTab } from "./hooks";
55
import { LoadingView } from "./loading";
66
import { ErrorCode, ErrorView } from "./error";
77
import { KeyIcon } from "./icons/key";
8+
import { CopyIcon } from "./icons/copy";
89
import styles from "./passwords.module.scss";
910

1011
interface LoginName {
@@ -43,7 +44,10 @@ export function PasswordsView() {
4344
fetchLoginNames(tab.id, tab.url);
4445
}, [tab?.url]);
4546

46-
const handleAutoFillPassword = async (loginName: LoginName) => {
47+
const handleAutoFillPassword = async (
48+
loginName: LoginName,
49+
action: "AUTO_FILL" | "COPY" = "AUTO_FILL",
50+
) => {
4751
if (tab?.id === undefined || tab?.url === undefined) return;
4852

4953
setError(undefined);
@@ -52,7 +56,7 @@ export function PasswordsView() {
5256
// Can't use GET_PASSWORD_FOR_LOGIN_NAME here
5357
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1292701
5458
await browser.runtime.sendMessage({
55-
cmd: "AUTO_FILL_PASSWORD",
59+
cmd: `${action}_PASSWORD`,
5660
tabId: tab.id,
5761
url: tab.url,
5862
loginName,
@@ -113,11 +117,20 @@ export function PasswordsView() {
113117
handleAutoFillPassword(loginName);
114118
}}
115119
>
116-
<KeyIcon />
120+
<KeyIcon title="Password item" />
117121
<div>
118122
<span>{loginName.username}</span>
119123
<span>{loginName.sites[0] ?? ""}</span>
120124
</div>
125+
<a
126+
onClick={(e) => {
127+
e.preventDefault();
128+
e.stopPropagation();
129+
handleAutoFillPassword(loginName, "COPY");
130+
}}
131+
>
132+
<CopyIcon title="Copy password to clipboard" />
133+
</a>
121134
</li>
122135
))}
123136
</ul>

0 commit comments

Comments
 (0)