Make the feed URL editable, with a copy button
Entries and history are keyed by feed id, so changing a URL keeps them -- the point being that a feed URL can carry an auth token that gets rotated. Changing it clears the stored ETag/Last-Modified, which belong to the old URL and could otherwise produce a bogus 304. The copy button cannot use navigator.clipboard: that needs a secure context and this is served over plain HTTP on a LAN address. Falls back to execCommand. Invalid input now returns 400 rather than 500. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RPyeapneuXrCdojsaiXGbe
This commit is contained in:
@@ -197,6 +197,9 @@ input[type=range]::-moz-range-thumb{width:12px;height:12px;border:0;border-radiu
|
||||
.field label{font-size:12px;color:var(--dim)}
|
||||
.field .hint{font-size:11.5px;color:var(--faint)}
|
||||
.check{display:flex;gap:8px;align-items:center;font-size:13.5px;margin-bottom:9px}
|
||||
.inline{display:flex;gap:6px;align-items:stretch}
|
||||
.inline input{flex:1;min-width:0}
|
||||
.inline .btn{white-space:nowrap;flex:none}
|
||||
.check input{width:16px;height:16px;accent-color:var(--accent)}
|
||||
.cardacts{display:flex;gap:8px;justify-content:flex-end;margin-top:16px}
|
||||
#toasts{position:fixed;bottom:88px;right:18px;display:flex;flex-direction:column;gap:8px;z-index:60}
|
||||
@@ -283,6 +286,35 @@ async function api(url,opts){
|
||||
if(!r.ok) throw new Error(await r.text().catch(()=>r.status)||r.status);
|
||||
return r.status===204?null:r.json().catch(()=>null);
|
||||
}
|
||||
// navigator.clipboard only exists in a secure context. Served over plain HTTP on a LAN
|
||||
// address it is undefined, so fall back to the old selection-based copy.
|
||||
async function copyText(text,btn){
|
||||
const flash=ok=>{
|
||||
if(!btn) return;
|
||||
const was=btn.textContent;
|
||||
btn.textContent=ok?'Copied':'Failed';
|
||||
setTimeout(()=>btn.textContent=was,1300);
|
||||
};
|
||||
try{
|
||||
if(navigator.clipboard&&window.isSecureContext){
|
||||
await navigator.clipboard.writeText(text);
|
||||
}else{
|
||||
const ta=document.createElement('textarea');
|
||||
ta.value=text; ta.setAttribute('readonly','');
|
||||
ta.style.cssText='position:fixed;top:-1000px;opacity:0';
|
||||
document.body.appendChild(ta);
|
||||
ta.select(); ta.setSelectionRange(0,ta.value.length);
|
||||
const ok=document.execCommand('copy');
|
||||
ta.remove();
|
||||
if(!ok) throw new Error('copy rejected');
|
||||
}
|
||||
flash(true);
|
||||
}catch(e){
|
||||
flash(false);
|
||||
toast('Could not copy automatically — select the URL and copy it manually',true);
|
||||
}
|
||||
}
|
||||
|
||||
function toast(msg,bad){
|
||||
const t=document.createElement('div');
|
||||
t.className='toast'+(bad?' bad':''); t.textContent=msg;
|
||||
@@ -623,13 +655,21 @@ function settingsModal(f){
|
||||
<span class="hint">Blank means no limit. The rest wait for the next scan.</span></div>
|
||||
<label class="check"><input type="checkbox" id="sauto" ${f.auto_download?'checked':''}> Download new episodes automatically</label>
|
||||
<label class="check"><input type="checkbox" id="sexp" ${f.allow_explicit?'checked':''}> Allow episodes marked explicit</label>
|
||||
<div class="field"><label>Feed URL</label><span class="hint" style="overflow-wrap:anywhere">${esc(f.url)}</span></div>
|
||||
<div class="field"><label>Feed URL</label>
|
||||
<div class="inline">
|
||||
<input type="text" id="surl" value="${esc(f.url)}" spellcheck="false">
|
||||
<button type="button" class="btn" id="scopy">Copy</button>
|
||||
</div>
|
||||
<span class="hint">Editing this keeps every episode and download — handy when an auth token
|
||||
in the URL is rotated. The feed is re-checked from scratch on the next scan.</span></div>
|
||||
<div class="cardacts"><button class="btn" onclick="closeModal()">Cancel</button>
|
||||
<button class="btn primary" id="ssave">Save</button></div>`);
|
||||
$('#scopy').onclick=()=>copyText($('#surl').value,$('#scopy'));
|
||||
$('#ssave').onclick=async()=>{
|
||||
const max=$('#smax').value;
|
||||
try{
|
||||
await api(`/api/feeds/${encodeURIComponent(f.id)}`,{method:'PATCH',body:JSON.stringify({
|
||||
url:$('#surl').value.trim(),
|
||||
folder:$('#sfolder').value.trim()||null,
|
||||
keywords:$('#skw').value.split(',').map(s=>s.trim()).filter(Boolean),
|
||||
max_new_per_check:max===''?null:Number(max),
|
||||
|
||||
Reference in New Issue
Block a user