feat(extension): add configurable Recipe Manager base URL settings
This commit is contained in:
parent
272ce1d2f0
commit
23aa097458
2
TODO.md
2
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
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue