From 21ccabfeac0ade67204f40bcb2e7378984dd2e73 Mon Sep 17 00:00:00 2001 From: Peter Nemcok Date: Fri, 15 May 2026 12:10:46 +0700 Subject: [PATCH] Supplements: save product URL for future reference Persists the URL pasted into the supplement editor as `entry.sourceUrl` so users can re-open the source page later. URL field is now visible even without an AI provider (Fetch button stays AI-gated). List rows show a hostname link next to the date range. --- js/supplements.js | 27 +++++++++++++++++++++------ styles.css | 2 ++ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/js/supplements.js b/js/supplements.js index 30278d1c..4a15bcd3 100644 --- a/js/supplements.js +++ b/js/supplements.js @@ -358,15 +358,16 @@ function _suppFormHtml(editIdx, s) { const editing = !!s; const ingredients = editing && s.ingredients ? s.ingredients : []; const periods = editing ? getSupplementPeriods(s) : [{ start: new Date().toISOString().slice(0, 10), end: null }]; + const sourceUrl = editing && s.sourceUrl ? s.sourceUrl : ''; return `
- ${hasAIProvider() ? `
-
+
+
- - + + ${hasAIProvider() ? `` : ''}
-
` : ''} +
@@ -461,11 +462,15 @@ export function openSupplementsEditor(editIdx) { const dateRange = pds.length === 1 ? `${fmtDate(pds[0].start)} \u2192 ${pds[0].end ? fmtDate(pds[0].end) : 'ongoing'}` : pds.map(p => `${fmtDate(p.start)}\u2192${p.end ? fmtDate(p.end) : 'now'}`).join(' \u00b7 '); + let sourceHost = ''; + if (s.sourceUrl) { + try { sourceHost = new URL(s.sourceUrl).hostname.replace(/^www\./, ''); } catch { sourceHost = ''; } + } html += `
${icon}
${escapeHTML(s.name)}${s.dosage ? ` ${escapeHTML(s.dosage)}` : ''}
-
${dateRange}
+
${dateRange}${sourceHost ? ` · ${escapeHTML(sourceHost)} ↗` : ''}
${s.ingredients?.length ? `
${s.ingredients.map(ing => { const total = ingredientDailyTotal(ing, s); const times = effectiveTimesPerDay(ing, s); @@ -540,11 +545,21 @@ export function saveSupplement(idx) { const ingredients = _collectIngredients(); const timesRaw = document.getElementById('supp-times')?.value.trim(); const timesNum = timesRaw ? parseFloat(timesRaw) : NaN; + const sourceUrlRaw = document.getElementById('supp-url')?.value.trim() || ''; + let sourceUrl = ''; + if (sourceUrlRaw) { + try { + const parsed = new URL(sourceUrlRaw); + if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') { showNotification('Product URL must be http or https', 'error'); return; } + sourceUrl = parsed.toString(); + } catch { showNotification('Invalid product URL', 'error'); return; } + } if (!state.importedData.supplements) state.importedData.supplements = []; const entry = { name, dosage, startDate, endDate, type, note }; if (sorted.length > 1) entry.periods = sorted; if (ingredients) entry.ingredients = ingredients; if (isFinite(timesNum) && timesNum > 0) entry.timesPerDay = timesNum; + if (sourceUrl) entry.sourceUrl = sourceUrl; if (idx >= 0) { state.importedData.supplements[idx] = entry; } else { diff --git a/styles.css b/styles.css index 76cef6f7..8ed8a92b 100755 --- a/styles.css +++ b/styles.css @@ -1614,6 +1614,8 @@ body.chat-open.chat-fullscreen .main { padding-right: 32px; } .supp-list-info { flex: 1; min-width: 0; } .supp-list-name { font-weight: 500; color: var(--text-primary); } .supp-list-meta { font-size: 11px; color: var(--text-muted); } +.supp-list-source { color: var(--accent); text-decoration: none; } +.supp-list-source:hover { text-decoration: underline; } .supp-list-note { font-size: 11px; color: var(--text-secondary); font-style: italic; margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } /* Supplement impact analysis (AI-driven) */ .supp-impact-section { border-top: 1px solid var(--border); padding: 12px 0; }