From 23aa09745880c76ea56d0fb0e234c85e1031233d Mon Sep 17 00:00:00 2001 From: Paul Huliganga Date: Wed, 25 Mar 2026 04:42:25 -0400 Subject: [PATCH] feat(extension): add configurable Recipe Manager base URL settings --- TODO.md | 2 +- browser-extension/README.md | 12 ++++--- browser-extension/options.html | 16 ++++++++- browser-extension/options.js | 61 ++++++++++++++++++++++++++++++++++ browser-extension/styles.css | 21 ++++++++++++ 5 files changed, 106 insertions(+), 6 deletions(-) create mode 100644 browser-extension/options.js diff --git a/TODO.md b/TODO.md index f45a68c..f6d47ce 100644 --- a/TODO.md +++ b/TODO.md @@ -44,7 +44,7 @@ MVP is functionally complete (core app + docs + tests). ### Phase 4: Browser Extension (after URL import stable) - [x] Scaffold browser extension project (Manifest v3) - [x] Add “Send to Recipe Manager” action to call import API -- [ ] Add extension settings for Recipe Manager base URL +- [x] Add extension settings for Recipe Manager base URL --- diff --git a/browser-extension/README.md b/browser-extension/README.md index 896da5d..481addb 100644 --- a/browser-extension/README.md +++ b/browser-extension/README.md @@ -1,13 +1,13 @@ # Browser Extension (Manifest v3) Scaffold -This folder contains the initial Manifest v3 scaffold for the Recipe Manager browser extension. +This folder contains the Manifest v3 browser extension for Recipe Manager URL import. ## Files - `manifest.json` — extension manifest -- `background.js` — service worker and context menu registration +- `background.js` — service worker and context menu import flow - `popup.html` / `popup.js` — basic action popup -- `options.html` — placeholder settings page +- `options.html` / `options.js` — settings UI and persistence for Recipe Manager base URL - `styles.css` — shared minimal styles ## Load locally (Chrome) @@ -17,4 +17,8 @@ This folder contains the initial Manifest v3 scaffold for the Recipe Manager bro 3. Click **Load unpacked** 4. Select this folder: `browser-extension/` -Future tasks will wire the context menu action to the import API and implement settings persistence for base URL. +## Settings + +Open the extension popup and click **Open Settings**. +Set the Recipe Manager base URL (defaults to `http://localhost:3000`). +The value is stored in `chrome.storage.sync` and used by the context menu import request. diff --git a/browser-extension/options.html b/browser-extension/options.html index 869a4af..42fb1ed 100644 --- a/browser-extension/options.html +++ b/browser-extension/options.html @@ -9,7 +9,21 @@

Recipe Manager Settings

-

Settings UI scaffold. Base URL wiring is a follow-up task.

+

Configure where import requests should be sent.

+ +
+ + +

+ +
+ diff --git a/browser-extension/options.js b/browser-extension/options.js new file mode 100644 index 0000000..f2e05de --- /dev/null +++ b/browser-extension/options.js @@ -0,0 +1,61 @@ +const DEFAULT_BASE_URL = 'http://localhost:3000'; + +function normalizeBaseUrl(rawBaseUrl) { + if (typeof rawBaseUrl !== 'string' || rawBaseUrl.trim().length === 0) { + return DEFAULT_BASE_URL; + } + + return rawBaseUrl.trim().replace(/\/+$/, ''); +} + +function isValidHttpUrl(value) { + try { + const url = new URL(value); + return url.protocol === 'http:' || url.protocol === 'https:'; + } catch { + return false; + } +} + +const form = document.getElementById('settings-form'); +const baseUrlInput = document.getElementById('base-url'); +const statusNode = document.getElementById('status'); + +function showStatus(message, isError = false) { + if (!statusNode) { + return; + } + + statusNode.textContent = message; + statusNode.style.color = isError ? '#991b1b' : '#166534'; +} + +async function loadSettings() { + const { recipeManagerBaseUrl } = await chrome.storage.sync.get({ + recipeManagerBaseUrl: DEFAULT_BASE_URL, + }); + + if (baseUrlInput) { + baseUrlInput.value = normalizeBaseUrl(recipeManagerBaseUrl); + } +} + +form?.addEventListener('submit', async (event) => { + event.preventDefault(); + + const normalizedBaseUrl = normalizeBaseUrl(baseUrlInput?.value ?? ''); + + if (!isValidHttpUrl(normalizedBaseUrl)) { + showStatus('Please enter a valid http(s) URL.', true); + baseUrlInput?.focus(); + return; + } + + await chrome.storage.sync.set({ recipeManagerBaseUrl: normalizedBaseUrl }); + showStatus('Settings saved.'); +}); + +loadSettings().catch((error) => { + console.error('[Recipe Manager Extension] Failed to load settings', error); + showStatus('Could not load settings.', true); +}); diff --git a/browser-extension/styles.css b/browser-extension/styles.css index af53e14..e2bf3ed 100644 --- a/browser-extension/styles.css +++ b/browser-extension/styles.css @@ -19,6 +19,22 @@ p { font-size: 13px; } +label { + display: block; + margin: 0 0 6px; + font-size: 13px; + font-weight: 600; +} + +input { + box-sizing: border-box; + width: 100%; + border: 1px solid #d1d5db; + border-radius: 6px; + padding: 8px 10px; + margin-bottom: 10px; +} + button { border: 1px solid #d1d5db; border-radius: 6px; @@ -26,3 +42,8 @@ button { background: #f9fafb; cursor: pointer; } + +#status { + min-height: 18px; + margin-bottom: 10px; +}