An x in a search box clears it (#291)

Once something is typed: at the box's right end, inside it. One click
handler for every box, which clears it and fires its input event, so each
box's own handler brings its list back whole. The browser's own x, drawn
by some browsers and not others, gives way to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-19 14:55:25 +00:00
parent d850c02501
commit 4c1b78408f
7 changed files with 49 additions and 7 deletions

View File

@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- A search box has an x at its right end once something is typed in it, which clears it: items, the feed list, the log in Admin and a Signal's icons (#291).
- A feed's, an OPML subscription's or a Patreon creator's settings have In All Subscriptions: off keeps its items out of All Subscriptions and its unread count, still there on its own page, in Signals and in Currently Listening (#288).
- A feed's settings have a Signals switch beside All Subscriptions, to keep its items out of every Signal (#289).
- An item sent as both HTML and Markdown, as Scripting News's are, has a choice beside its buttons to show it as the publisher's HTML or as the Markdown, kept from one item to the next (#285).

View File

@@ -537,7 +537,7 @@ a{color:var(--accent)}
position:sticky;top:0;
}
#topbar .grow{flex:1}
#topbar #epSearch{width:260px;flex:none}
#topbar #epBox{width:260px;flex:none}
.tgroup{display:flex;flex:none;border:1px solid var(--line);border-radius:8px;overflow:hidden;background:var(--panel2)}
.tgroup button,.tgroup a{padding:5px 11px;min-width:34px;color:var(--dim);font-size:14px;line-height:1.3}
.tgroup a{display:inline-grid;place-items:center}
@@ -567,6 +567,17 @@ input[type=search],input[type=text],input[type=password],input[type=number],sele
border-radius:8px;padding:7px 10px;font:inherit;font-size:13.5px;
}
input:focus,select:focus{outline:0;border-color:var(--accent)}
/* A search box and the x that clears it, inside its right end, there only once something is typed
(#291). The browser's own x, in some browsers and not others, gives way to it. */
.clearable{position:relative;display:flex;align-items:center;min-width:0}
/* With [type=search], or the phone's padding for every text box, later on, takes the room back. */
.clearable>input[type=search]{flex:1;min-width:0;padding-right:32px}
.clearable>input::-webkit-search-cancel-button{-webkit-appearance:none;appearance:none}
.clearx{position:absolute;right:4px;top:50%;transform:translateY(-50%);display:grid;place-items:center;width:26px;height:26px;padding:0;border-radius:50%;color:var(--faint)}
.clearx:hover{color:var(--fg)}
.clearx svg{width:11px;height:11px}
.clearable:has(>input:placeholder-shown) .clearx{display:none}
.inline .clearable{flex:1}
/* The wider left gutter is the folder triangle's; everything in the list shifts with it, so the
feeds still line up with the places above. */
#feedlist{overflow-y:auto;padding:0 8px 12px 16px;flex:1;min-height:0}
@@ -1251,7 +1262,7 @@ body:has(#player.on) #status{padding-bottom:3px}
padding-left:calc(8px + var(--safe-l));padding-right:calc(8px + var(--safe-r));
}
#topbar .grow,.tgroup.item{display:none}
#topbar #epSearch{width:auto;flex:1;min-width:0}
#topbar #epBox{width:auto;flex:1;min-width:0}
.tgroup button,.tgroup a{padding:4px 8px;min-width:32px}
.fhead.slim .acts{flex:1 1 100%}
.iconbtn{width:38px;height:38px}
@@ -1359,7 +1370,7 @@ body:has(#player.on) #status{padding-bottom:3px}
input[type=search],input[type=text],input[type=password],input[type=number],select{
font-size:17px;background:var(--panel2);border-color:var(--edge);border-radius:10px;padding:9px 12px;
}
#topbar #epSearch{font-size:16px;border-radius:999px;padding:9px 14px}
#topbar #epSearch{font-size:16px;border-radius:999px;padding:9px 34px 9px 14px}
input:focus,select:focus{border-color:var(--accent)}
/* The feed's name as the page's large title, the way Podcasts heads a show. */

View File

@@ -28,7 +28,7 @@
<button id="tbFlag" title="Pin the selected item, so it is never deleted" aria-label="Pin" data-icon="pin" disabled></button>
</div>
<span class="grow"></span>
<input type="search" id="epSearch" placeholder="Search items…">
<span class="clearable" id="epBox"><input type="search" id="epSearch" placeholder="Search items…"><button type="button" class="clearx" title="Clear" aria-label="Clear" data-icon="close"></button></span>
<div class="tgroup" id="admintools">
<button id="prefs" title="Settings" aria-label="Settings" data-icon="settings"></button>
<a id="admin" href="/admin" title="Admin: the server's settings, accounts and the log" aria-label="Admin" data-icon="admin"></a>
@@ -36,7 +36,7 @@
</header>
<div id="shell">
<aside id="sidebar">
<div class="searchwrap"><input type="search" id="feedFilter" placeholder="Filter feeds…"></div>
<div class="searchwrap"><span class="clearable"><input type="search" id="feedFilter" placeholder="Filter feeds…"><button type="button" class="clearx" title="Clear" aria-label="Clear" data-icon="close"></button></span></div>
<div id="feedlist"></div>
<div class="sidefoot">
<div class="who"><span id="who"></span><button id="signout" title="Sign out" aria-label="Sign out" data-icon="signout"></button></div>

View File

@@ -262,7 +262,7 @@ function drawLogView(){
<option value="WARN">Warnings and errors</option>
<option value="ERROR">Errors only</option>
</select>
<input type="search" id="logq" class="grow" placeholder="Filter…">
${searchBox('<input type="search" id="logq" placeholder="Filter…">','grow')}
<label class="check" style="margin:0"><input type="checkbox" id="logfollow" checked> Follow</label>
<button class="btn ico" id="logcopy" title="Copy what is showing" aria-label="Copy what is showing">${ICON.copy}</button>
</div>

View File

@@ -142,7 +142,7 @@ function signalModal(sg?: Signal){
aria-expanded="false"></button>
<input type="text" id="sgname" value="${esc(sg?.name||'')}" placeholder="Formula 1"></div>
<div id="sgpicker" hidden>
<div class="inline"><input type="search" id="sgiconq" placeholder="Search icons: robot, car, music…" autocomplete="off">
<div class="inline">${searchBox('<input type="search" id="sgiconq" placeholder="Search icons: robot, car, music…" autocomplete="off">')}
<button type="button" class="dlink" id="sgjev" title="Pick an icon for me">Auto</button>
<button type="button" class="dlink" id="sgnoicon">Initials</button></div>
<div class="icongrid" id="sgicons" role="listbox" aria-label="Icons"></div>

View File

@@ -61,6 +61,20 @@ const EQ='<span class="eq" aria-hidden="true"><i></i><i></i><i></i></span>';
// The page's own buttons name their icon; this draws it in, ahead of any label they carry.
for(const b of $$('[data-icon]')) b.insertAdjacentHTML('afterbegin',ICON[b.dataset.icon!]);
/// A search box with an x at its right end that clears it, shown once something is typed (#291).
const searchBox=(input: string, cls='')=>
`<span class="clearable ${cls}">${input}<button type="button" class="clearx" title="Clear" aria-label="Clear">${ICON.close}</button></span>`;
// Every box's x, wherever it is drawn: cleared as if the typing were deleted, so the box's own input
// handler runs and its list comes back whole.
document.addEventListener('click',(e: MouseEvent)=>{
const x=(e.target as Element).closest?.('.clearx');
const box=x?.parentElement?.querySelector('input');
if(!box) return;
box.value='';
box.dispatchEvent(new Event('input',{bubbles:true}));
box.focus();
});
/// An episode whose DeadAir copy is still being made: it plays as it is until then, and the line
/// becomes how much shorter it is once the copy is ready (the dead_air event).
const DEAD_PENDING='<span class="dapend" title="DeadAir is still working on this episode. Until it is done, it plays at its full length.">Cutting dead air…</span>';