diff --git a/package-lock.json b/package-lock.json
index 9f0dfaaf..d6f68bd0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "buttercup-browser-extension",
- "version": "3.0.0",
+ "version": "3.1.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "buttercup-browser-extension",
- "version": "3.0.0",
+ "version": "3.1.0",
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.23.3",
@@ -15,6 +15,7 @@
"@blueprintjs/core": "^4.20.2",
"@blueprintjs/icons": "^4.16.0",
"@blueprintjs/popover2": "^1.14.11",
+ "@blueprintjs/select": "^4.9.24",
"@buttercup/channel-queue": "^1.4.0",
"@buttercup/locust": "^2.2.1",
"@buttercup/ui": "^6.2.2",
@@ -1951,6 +1952,28 @@
"react-dom": "^16.8.0 || ^17 || ^18"
}
},
+ "node_modules/@blueprintjs/select": {
+ "version": "4.9.24",
+ "resolved": "https://registry.npmjs.org/@blueprintjs/select/-/select-4.9.24.tgz",
+ "integrity": "sha512-OTjesxH/7UZvM7yAdHJ5u3sIjX1N8Rs4CQQ22AfqNl82SIROqkuXI31XEl6YNX1GsYfmAMiw0K7XohEKOMXR5g==",
+ "dev": true,
+ "dependencies": {
+ "@blueprintjs/core": "^4.20.2",
+ "@blueprintjs/popover2": "^1.14.11",
+ "classnames": "^2.3.1",
+ "tslib": "~2.5.0"
+ },
+ "peerDependencies": {
+ "@types/react": "^16.14.32 || 17 || 18",
+ "react": "^16.8 || 17 || 18",
+ "react-dom": "^16.8 || 17 || 18"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@buttercup/channel-queue": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@buttercup/channel-queue/-/channel-queue-1.4.0.tgz",
@@ -16137,6 +16160,18 @@
}
}
},
+ "@blueprintjs/select": {
+ "version": "4.9.24",
+ "resolved": "https://registry.npmjs.org/@blueprintjs/select/-/select-4.9.24.tgz",
+ "integrity": "sha512-OTjesxH/7UZvM7yAdHJ5u3sIjX1N8Rs4CQQ22AfqNl82SIROqkuXI31XEl6YNX1GsYfmAMiw0K7XohEKOMXR5g==",
+ "dev": true,
+ "requires": {
+ "@blueprintjs/core": "^4.20.2",
+ "@blueprintjs/popover2": "^1.14.11",
+ "classnames": "^2.3.1",
+ "tslib": "~2.5.0"
+ }
+ },
"@buttercup/channel-queue": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@buttercup/channel-queue/-/channel-queue-1.4.0.tgz",
diff --git a/package.json b/package.json
index e4bb0578..a9221265 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "buttercup-browser-extension",
- "version": "3.0.0",
+ "version": "3.1.0",
"description": "Buttercup browser extension",
"exports": "./dist/background/index.js",
"type": "module",
@@ -61,6 +61,7 @@
"@blueprintjs/core": "^4.20.2",
"@blueprintjs/icons": "^4.16.0",
"@blueprintjs/popover2": "^1.14.11",
+ "@blueprintjs/select": "^4.9.24",
"@buttercup/channel-queue": "^1.4.0",
"@buttercup/locust": "^2.2.1",
"@buttercup/ui": "^6.2.2",
diff --git a/source/background/services/config.ts b/source/background/services/config.ts
index a0e34259..aff91eda 100644
--- a/source/background/services/config.ts
+++ b/source/background/services/config.ts
@@ -1,8 +1,9 @@
import { getSyncValue, setSyncValue } from "./storage.js";
-import { Configuration, SyncStorageItem } from "../types.js";
+import { Configuration, InputButtonType, SyncStorageItem } from "../types.js";
const DEFAULTS: Configuration = {
entryIcons: true,
+ inputButtonDefault: InputButtonType.LargeButton,
saveNewLogins: true,
theme: "light",
useSystemTheme: true
diff --git a/source/popup/components/pages/SettingsPage.tsx b/source/popup/components/pages/SettingsPage.tsx
index 1072cd07..ae140ca8 100644
--- a/source/popup/components/pages/SettingsPage.tsx
+++ b/source/popup/components/pages/SettingsPage.tsx
@@ -1,6 +1,7 @@
import React, { Fragment, useCallback, useMemo, useState } from "react";
import styled from "styled-components";
-import { Alert, Button, Callout, Classes, Intent, Switch } from "@blueprintjs/core";
+import { Alert, Button, Callout, Classes, Intent, MenuItem, Switch } from "@blueprintjs/core";
+import { ItemRendererProps, Select } from "@blueprintjs/select";
import { t } from "../../../shared/i18n/trans.js";
import { useConfig } from "../../../shared/hooks/config.js";
import { ErrorMessage } from "../../../shared/components/ErrorMessage.js";
@@ -9,6 +10,11 @@ import { getToaster } from "../../../shared/services/notifications.js";
import { localisedErrorMessage } from "../../../shared/library/error.js";
import { useAllLoginCredentials } from "../../hooks/credentials.js";
import { createNewTab, getExtensionURL } from "../../../shared/library/extension.js";
+import { InputButtonType } from "../../types.js";
+
+interface InputButtonTypeItem {
+ name: string, type: InputButtonType;
+}
const Container = styled.div`
display: flex;
@@ -26,11 +32,37 @@ const SettingSection = styled(Callout)`
padding: 9px;
`;
+function renderInputButtonTypeItem(item: InputButtonTypeItem, props: ItemRendererProps) {
+ const { handleClick, handleFocus, modifiers } = props;
+ return (
+
+ );
+}
+
export function SettingsPage() {
const [config, configError, setValue] = useConfig();
const [showConfirmReset, setShowConfirmReset] = useState(false);
const { value: allCredentials } = useAllLoginCredentials();
const hasSavedCredentials = useMemo(() => Array.isArray(allCredentials) && allCredentials.length > 0, [allCredentials]);
+ const inputButtonItems: Array = useMemo(() => Object.values(InputButtonType).map(type => ({
+ name: t(`config.input-button-type.${type}`),
+ type
+ })), []);
+ const activeInputButtonItem = useMemo(
+ () => inputButtonItems.find(item => item.type === config?.inputButtonDefault),
+ [config, inputButtonItems]);
+ const handleInputButtonItemSelect = useCallback((item: InputButtonTypeItem) => {
+ setValue("inputButtonDefault", item.type);
+ }, [setValue]);
const handleOpenDisabledDomains = useCallback(async () => {
try {
await createNewTab(getExtensionURL("full.html#/disabled-domains"));
@@ -110,6 +142,22 @@ export function SettingsPage() {
)}
+
+
+
;
export interface Configuration {
entryIcons: boolean;
+ inputButtonDefault: InputButtonType;
saveNewLogins: boolean;
theme: "light" | "dark";
useSystemTheme: boolean;
@@ -107,6 +108,11 @@ export interface ElementRect {
height: number;
}
+export enum InputButtonType {
+ InnerIcon = "innericon",
+ LargeButton = "largebutton"
+}
+
export enum InputType {
OTP = "otp",
UserPassword = "user-password"
diff --git a/source/tab/services/config.ts b/source/tab/services/config.ts
new file mode 100644
index 00000000..99b7b386
--- /dev/null
+++ b/source/tab/services/config.ts
@@ -0,0 +1,13 @@
+import { Layerr } from "layerr";
+import { sendBackgroundMessage } from "../../shared/services/messaging.js";
+import { BackgroundMessageType, Configuration } from "../types.js";
+
+export async function getConfig(): Promise {
+ const resp = await sendBackgroundMessage({
+ type: BackgroundMessageType.GetConfiguration
+ });
+ if (resp.error) {
+ throw new Layerr(resp.error, "Failed fetching configuration");
+ }
+ return resp.config;
+}
diff --git a/source/tab/services/form.ts b/source/tab/services/form.ts
index 667eb787..f349e246 100644
--- a/source/tab/services/form.ts
+++ b/source/tab/services/form.ts
@@ -27,7 +27,7 @@ export function fillFormDetails(frameEvent: FrameEvent) {
export async function initialise() {
// Watch for forms
- waitAndAttachLaunchButtons((input, loginTarget, inputType) => {
+ await waitAndAttachLaunchButtons((input, loginTarget, inputType) => {
FORM.currentFormID = ulid();
FORM.currentLoginTarget = loginTarget;
if (FRAME.isTop) {
diff --git a/source/tab/services/formDetection.ts b/source/tab/services/formDetection.ts
index 5668dda4..eb78ada2 100644
--- a/source/tab/services/formDetection.ts
+++ b/source/tab/services/formDetection.ts
@@ -3,6 +3,7 @@ import { attachLaunchButton } from "../ui/launch.js";
import { watchCredentialsOnTarget } from "./logins/watcher.js";
import { processTargetAutoLogin } from "./autoLogin.js";
import { InputType } from "../types.js";
+import { getConfig } from "./config.js";
const TARGET_SEARCH_INTERVAL = 1000;
@@ -28,19 +29,26 @@ function onIdentifiedTarget(callback: (target: LoginTarget) => void) {
};
}
-export function waitAndAttachLaunchButtons(
+export async function waitAndAttachLaunchButtons(
onInputActivate: (input: HTMLInputElement, loginTarget: LoginTarget, inputType: InputType) => void
) {
+ const config = await getConfig();
onIdentifiedTarget((loginTarget: LoginTarget) => {
const { otpField, usernameField, passwordField } = loginTarget;
if (otpField) {
- attachLaunchButton(otpField, (el) => onInputActivate(el, loginTarget, InputType.OTP));
+ attachLaunchButton(otpField, config.inputButtonDefault, (el) =>
+ onInputActivate(el, loginTarget, InputType.OTP)
+ );
}
if (passwordField) {
- attachLaunchButton(passwordField, (el) => onInputActivate(el, loginTarget, InputType.UserPassword));
+ attachLaunchButton(passwordField, config.inputButtonDefault, (el) =>
+ onInputActivate(el, loginTarget, InputType.UserPassword)
+ );
}
if (usernameField) {
- attachLaunchButton(usernameField, (el) => onInputActivate(el, loginTarget, InputType.UserPassword));
+ attachLaunchButton(usernameField, config.inputButtonDefault, (el) =>
+ onInputActivate(el, loginTarget, InputType.UserPassword)
+ );
}
watchCredentialsOnTarget(loginTarget);
processTargetAutoLogin(loginTarget).catch(console.error);
diff --git a/source/tab/ui/launch.ts b/source/tab/ui/launch.ts
index 4991eadf..5ead1f77 100644
--- a/source/tab/ui/launch.ts
+++ b/source/tab/ui/launch.ts
@@ -6,11 +6,16 @@ import { onBodyWidthResize } from "../library/resize.js";
import { getExtensionURL } from "../../shared/library/extension.js";
import BUTTON_BACKGROUND_IMAGE_RES from "../../../resources/content-button-background.png";
import INPUT_BACKGROUND_IMAGE_RES from "../../../resources/buttercup-simple-150.png";
+import { InputButtonType } from "../types.js";
const BUTTON_BACKGROUND_IMAGE = getExtensionURL(BUTTON_BACKGROUND_IMAGE_RES);
const INPUT_BACKGROUND_IMAGE = getExtensionURL(INPUT_BACKGROUND_IMAGE_RES);
-export function attachLaunchButton(input: HTMLInputElement, onClick: (input: HTMLInputElement) => void) {
+export function attachLaunchButton(
+ input: HTMLInputElement,
+ buttonType: InputButtonType,
+ onClick: (input: HTMLInputElement) => void
+): void {
if (input.dataset.bcup === "attached" || itemIsIgnored(input)) {
return;
}
@@ -24,8 +29,11 @@ export function attachLaunchButton(input: HTMLInputElement, onClick: (input: HTM
setTimeout(tryToAttach, 250);
return;
}
- renderButtonStyle(input, () => onClick(input), tryToAttach, bounds);
- // renderInternalStyle(input, () => onClick(input), tryToAttach, bounds);
+ if (buttonType === InputButtonType.LargeButton) {
+ renderButtonStyle(input, () => onClick(input), tryToAttach, bounds);
+ } else if (buttonType === InputButtonType.InnerIcon) {
+ renderInternalStyle(input, () => onClick(input), tryToAttach, bounds);
+ }
};
tryToAttach();
}
@@ -41,6 +49,7 @@ function renderInternalStyle(
const imageSize = height * 0.6;
const rightOffset = 8;
const buttonArea = imageSize + rightOffset + 4;
+ const originalAutocomplete = input.getAttribute("autocomplete") ?? null;
setStyle(input, {
backgroundImage: `url(${INPUT_BACKGROUND_IMAGE})`,
backgroundSize: `${imageSize}px`,
@@ -52,16 +61,21 @@ function renderInternalStyle(
if (event.offsetX >= input.offsetWidth - buttonArea) {
event.preventDefault();
event.stopPropagation();
- // toggleInputDialog(input, DIALOG_TYPE_ENTRY_PICKER);
onClick();
}
};
input.onmousemove = (event) => {
if (event.offsetX >= input.offsetWidth - buttonArea) {
+ input.setAttribute("autocomplete", "off");
setStyle(input, {
cursor: "pointer"
});
} else {
+ if (originalAutocomplete) {
+ input.setAttribute("autocomplete", originalAutocomplete);
+ } else {
+ input.removeAttribute("autocomplete");
+ }
setStyle(input, {
cursor: "unset"
});