Fix L17/L18/L56: regex validation, terminal visibility, SponsorBlock warning
L17: TemplateManager urlPattern field now validates the regex on each keystroke;
an error message appears below the input and Save is blocked until fixed.
L18: Terminal nav item is hidden when customCommandEnabled is false -- no more
dead-end gated view. Sidebar gains showTerminal prop; App.tsx reads the
setting and passes it through.
L56: SponsorBlock Categories Field shows a 'warning' validationMessage when the
toggle is ON but no categories are selected, making the silent no-op visible.
typecheck + 242 tests + eslint + prettier green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+3
-3
@@ -410,9 +410,9 @@ token system + shared primitives (UI/SIMP — high value, larger effort) · i18n
|
||||
already that union).
|
||||
- [ ] **L16 — `relTime`/`formatWhen` verbosity.** `relTime` returns unbounded "N d ago"; History's
|
||||
`formatWhen` always appends the year, even for the current year.
|
||||
- [ ] **L17 — TemplateManager regex not validated.** An invalid `urlPattern` is accepted with no
|
||||
- [x] **L17 — TemplateManager regex not validated.** An invalid `urlPattern` is accepted with no
|
||||
feedback and silently never matches (main's `matchesUrl` swallows the error).
|
||||
- [ ] **L18 — Terminal nav item shown when custom commands are off** — leads to a gated dead-end
|
||||
- [x] **L18 — Terminal nav item shown when custom commands are off** — leads to a gated dead-end
|
||||
view; consider hiding/disabling it.
|
||||
- [ ] **L19 — `App.tsx` focus hack** — `setTimeout(() => …focus(), 60)` after a tab switch.
|
||||
- [ ] **L20 — `youtubePlayerClient` is free text** with no allowlist/validation; a typo passes
|
||||
@@ -486,7 +486,7 @@ token system + shared primitives (UI/SIMP — high value, larger effort) · i18n
|
||||
- [x] **L54 — DownloadBar preview `<img>` has no `onError` fallback** (inconsistent with `MediaThumb`'s retry).
|
||||
- [x] **L55 — QueueItem meta line can show size twice** — a probed-format `quality` label already
|
||||
contains the size, then `sizeLabel` is appended again.
|
||||
- [ ] **L56 — SponsorBlock ON with zero categories silently no-ops** (`buildArgs` guards on
|
||||
- [x] **L56 — SponsorBlock ON with zero categories silently no-ops** (`buildArgs` guards on
|
||||
`length > 0`) with no UI warning.
|
||||
- [x] **L57 — Scheduling a past datetime silently downloads now** (`buildItem` future-only guard),
|
||||
no feedback that the schedule was ignored.
|
||||
|
||||
@@ -38,6 +38,7 @@ function App(): React.JSX.Element {
|
||||
const theme = useSettings((s) => s.theme)
|
||||
const accentColor = useSettings((s) => s.accentColor)
|
||||
const updateSettings = useSettings((s) => s.update)
|
||||
const showTerminal = useSettings((s) => s.customCommandEnabled)
|
||||
const isDark = useResolvedDark()
|
||||
const [tab, setTab] = useState<TabValue>('downloads')
|
||||
const [paletteOpen, setPaletteOpen] = useState(false)
|
||||
@@ -171,6 +172,7 @@ function App(): React.JSX.Element {
|
||||
version={version}
|
||||
collapsed={collapsed}
|
||||
onToggleCollapsed={toggleCollapsed}
|
||||
showTerminal={showTerminal}
|
||||
/>
|
||||
|
||||
<main className={styles.content}>
|
||||
|
||||
@@ -196,7 +196,15 @@ export function DownloadOptionsForm({ value, onChange }: Props): React.JSX.Eleme
|
||||
onChange={(v) => setOpt('sponsorBlockMode', v as SponsorBlockMode)}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Categories">
|
||||
<Field
|
||||
label="Categories"
|
||||
validationState={value.sponsorBlockCategories.length === 0 ? 'warning' : 'none'}
|
||||
validationMessage={
|
||||
value.sponsorBlockCategories.length === 0
|
||||
? 'No categories selected — no segments will be skipped.'
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<div className={styles.categoryGrid}>
|
||||
{SPONSORBLOCK_CATEGORIES.map((cat) => (
|
||||
<Checkbox
|
||||
|
||||
@@ -152,6 +152,8 @@ interface SidebarProps {
|
||||
version: string
|
||||
collapsed: boolean
|
||||
onToggleCollapsed: () => void
|
||||
/** Show the Terminal nav item (only when custom commands are enabled) */
|
||||
showTerminal: boolean
|
||||
}
|
||||
|
||||
export function Sidebar({
|
||||
@@ -162,7 +164,8 @@ export function Sidebar({
|
||||
onSetTheme,
|
||||
version,
|
||||
collapsed,
|
||||
onToggleCollapsed
|
||||
onToggleCollapsed,
|
||||
showTerminal
|
||||
}: SidebarProps): React.JSX.Element {
|
||||
const styles = useStyles()
|
||||
const focus = useFocusStyles()
|
||||
@@ -214,7 +217,7 @@ export function Sidebar({
|
||||
</div>
|
||||
|
||||
<div className={styles.nav}>
|
||||
{NAV.map((n) => {
|
||||
{NAV.filter((n) => n.value !== 'terminal' || showTerminal).map((n) => {
|
||||
const active = tab === n.value
|
||||
const btn = (
|
||||
<button
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
Button,
|
||||
Field,
|
||||
Input,
|
||||
Textarea,
|
||||
Caption1,
|
||||
@@ -103,10 +104,21 @@ export function TemplateManager(): React.JSX.Element {
|
||||
setDraft({ id: t.id, name: t.name, args: t.args, urlPattern: t.urlPattern ?? '' })
|
||||
}
|
||||
|
||||
function patternError(): string | undefined {
|
||||
const p = draft?.urlPattern.trim()
|
||||
if (!p) return undefined
|
||||
try {
|
||||
new RegExp(p)
|
||||
return undefined
|
||||
} catch {
|
||||
return 'Invalid regex — this pattern will never match.'
|
||||
}
|
||||
}
|
||||
|
||||
function commit(): void {
|
||||
if (!draft) return
|
||||
const name = draft.name.trim()
|
||||
if (!name) return
|
||||
if (!name || patternError()) return
|
||||
const urlPattern = draft.urlPattern.trim()
|
||||
save({
|
||||
id: draft.id ?? newId('tpl'),
|
||||
@@ -165,11 +177,16 @@ export function TemplateManager(): React.JSX.Element {
|
||||
resize="vertical"
|
||||
onChange={(_, d) => setDraft({ ...draft, args: d.value })}
|
||||
/>
|
||||
<Input
|
||||
value={draft.urlPattern}
|
||||
placeholder="Auto-apply to URLs matching (regex, optional) — e.g. soundcloud\.com"
|
||||
onChange={(_, d) => setDraft({ ...draft, urlPattern: d.value })}
|
||||
/>
|
||||
<Field
|
||||
validationState={patternError() ? 'error' : 'none'}
|
||||
validationMessage={patternError()}
|
||||
>
|
||||
<Input
|
||||
value={draft.urlPattern}
|
||||
placeholder="Auto-apply to URLs matching (regex, optional) — e.g. soundcloud\.com"
|
||||
onChange={(_, d) => setDraft({ ...draft, urlPattern: d.value })}
|
||||
/>
|
||||
</Field>
|
||||
<div className={styles.formActions}>
|
||||
<Button appearance="subtle" icon={<DismissRegular />} onClick={() => setDraft(null)}>
|
||||
Cancel
|
||||
|
||||
Reference in New Issue
Block a user