feat(extension): add configurable Recipe Manager base URL settings

This commit is contained in:
Paul Huliganga 2026-03-25 04:42:25 -04:00
parent 272ce1d2f0
commit 23aa097458
5 changed files with 106 additions and 6 deletions

View File

@ -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
---

View File

@ -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.

View File

@ -9,7 +9,21 @@
<body>
<main>
<h1>Recipe Manager Settings</h1>
<p>Settings UI scaffold. Base URL wiring is a follow-up task.</p>
<p>Configure where import requests should be sent.</p>
<form id="settings-form">
<label for="base-url">Recipe Manager base URL</label>
<input
id="base-url"
name="baseUrl"
type="url"
placeholder="http://localhost:3000"
required
/>
<p id="status" aria-live="polite"></p>
<button type="submit">Save</button>
</form>
</main>
<script type="module" src="options.js"></script>
</body>
</html>

View File

@ -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);
});

View File

@ -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;
}