Compare commits
No commits in common. "e420a47d0374dfcb5a5b31d1f445e30fb2388736" and "936dc5db12e47781c69e446c942e3b8c8a1a0c2b" have entirely different histories.
e420a47d03
...
936dc5db12
Binary file not shown.
|
Before Width: | Height: | Size: 3.6 KiB |
|
|
@ -9,7 +9,7 @@ import { EntryIcon } from './MediaEntry';
|
|||
// ─── API ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
const LEET_BASE = 'https://hackerswithstyle.se/leet';
|
||||
const PAGE_SIZE = 1000;
|
||||
const PAGE_SIZE = 50;
|
||||
const DOWNLOAD_DIR = '/sd/downloads';
|
||||
|
||||
function leetFetch(path: string, query?: Record<string, string>) {
|
||||
|
|
@ -236,13 +236,7 @@ export default function SearchAssembly64({ config, setConfig, onClose }: SearchA
|
|||
}
|
||||
};
|
||||
|
||||
const handleSearch = () => {
|
||||
setFilterText('');
|
||||
const trimmed = query.trim();
|
||||
const q = trimmed && !trimmed.includes(':') ? `name:${trimmed}` : trimmed;
|
||||
if (q !== query) setQuery(q);
|
||||
doSearch(q, categoryFilter, 0);
|
||||
};
|
||||
const handleSearch = () => { setFilterText(''); doSearch(query, categoryFilter, 0); };
|
||||
const handleLoadMore = () => doSearch(query, categoryFilter, offset, true);
|
||||
|
||||
const applyPreset = (group: PresetGroup, value: PresetValue) => {
|
||||
|
|
@ -358,11 +352,25 @@ export default function SearchAssembly64({ config, setConfig, onClose }: SearchA
|
|||
<img src={`${import.meta.env.BASE_URL}assets/favicon.a64.png`} className="w-5 h-5 flex-shrink-0 object-contain" alt="" aria-hidden="true" />
|
||||
<span className="text-sm font-semibold text-neutral-700">Assembly64</span>
|
||||
</div>
|
||||
{hasSearched && (
|
||||
<button
|
||||
onClick={() => setShowFilter(v => !v)}
|
||||
className={`relative p-1.5 rounded-lg transition-colors ${showFilter ? 'bg-blue-100 text-blue-600' : 'hover:bg-neutral-100 text-neutral-400 hover:text-neutral-600'}`}
|
||||
title="Filter & sort results"
|
||||
>
|
||||
<SlidersHorizontal className="w-4 h-4" />
|
||||
{activeFilters > 0 && (
|
||||
<span className="absolute -top-0.5 -right-0.5 w-3.5 h-3.5 rounded-full bg-blue-600 text-white text-[9px] flex items-center justify-center font-bold">
|
||||
{activeFilters}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Search input + presets + categories */}
|
||||
<div className="flex-shrink-0 px-4 pt-3 pb-3 border-b border-neutral-200/70">
|
||||
<div className="flex items-center gap-1.5 mb-2">
|
||||
<div className="flex gap-2 mb-2">
|
||||
<div className="relative flex-1">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-neutral-400 pointer-events-none" />
|
||||
<input
|
||||
|
|
@ -389,28 +397,10 @@ export default function SearchAssembly64({ config, setConfig, onClose }: SearchA
|
|||
<button
|
||||
onClick={handleSearch}
|
||||
disabled={isSearching}
|
||||
className="p-1.5 rounded-lg hover:bg-neutral-100 disabled:opacity-40 transition-colors flex-shrink-0 text-neutral-400 hover:text-blue-600"
|
||||
title="Search"
|
||||
aria-label="Search"
|
||||
className="px-4 py-2.5 bg-blue-600 text-white rounded-xl text-sm font-medium hover:bg-blue-700 disabled:opacity-40 transition-colors"
|
||||
>
|
||||
{isSearching
|
||||
? <Loader2 className="w-5 h-5 animate-spin text-blue-500" />
|
||||
: <Search className="w-5 h-5" />}
|
||||
{isSearching ? <Loader2 className="w-4 h-4 animate-spin" /> : 'Search'}
|
||||
</button>
|
||||
{hasSearched && (
|
||||
<button
|
||||
onClick={() => setShowFilter(v => !v)}
|
||||
className={`relative p-1.5 rounded-lg transition-colors flex-shrink-0 ${showFilter ? 'bg-blue-100 text-blue-600' : 'hover:bg-neutral-100 text-neutral-400 hover:text-neutral-600'}`}
|
||||
title="Filter & sort results"
|
||||
>
|
||||
<SlidersHorizontal className="w-4 h-4" />
|
||||
{activeFilters > 0 && (
|
||||
<span className="absolute -top-0.5 -right-0.5 w-3.5 h-3.5 rounded-full bg-blue-600 text-white text-[9px] flex items-center justify-center font-bold">
|
||||
{activeFilters}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{presets.length > 0 && (
|
||||
|
|
@ -541,7 +531,7 @@ export default function SearchAssembly64({ config, setConfig, onClose }: SearchA
|
|||
{item.handle && !item.group && (
|
||||
<span className="text-xs text-neutral-400">{item.handle}</span>
|
||||
)}
|
||||
{!!item.year && (
|
||||
{item.year && (
|
||||
<span className="text-xs text-neutral-400 flex items-center gap-0.5">
|
||||
<Calendar className="w-3 h-3" />{item.year}
|
||||
</span>
|
||||
|
|
@ -549,7 +539,7 @@ export default function SearchAssembly64({ config, setConfig, onClose }: SearchA
|
|||
{item.event && (
|
||||
<span className="text-xs text-neutral-400 truncate">{item.event}</span>
|
||||
)}
|
||||
{!!item.place && (
|
||||
{item.place && (
|
||||
<span className="text-xs text-neutral-400">#{item.place}</span>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -231,13 +231,28 @@ export default function SearchCSDbNG({ config, setConfig, onClose }: SearchCSDbN
|
|||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
<div className="flex items-center gap-2 flex-1 py-3 min-w-0">
|
||||
<img src={`${import.meta.env.BASE_URL}assets/favicon.csdbng.png`} className="h-5 flex-shrink-0 object-contain" alt="" aria-hidden="true" />
|
||||
<Archive className="w-5 h-5 flex-shrink-0 text-neutral-500" aria-hidden="true" />
|
||||
<span className="text-sm font-semibold text-neutral-700">CSDb-ng</span>
|
||||
</div>
|
||||
{hasSearched && (
|
||||
<button
|
||||
onClick={() => setShowFilter(v => !v)}
|
||||
className={`relative p-1.5 rounded-lg transition-colors ${showFilter ? 'bg-blue-100 text-blue-600' : 'hover:bg-neutral-100 text-neutral-400 hover:text-neutral-600'}`}
|
||||
title="Filter & sort results"
|
||||
>
|
||||
<SlidersHorizontal className="w-4 h-4" />
|
||||
{activeFilters > 0 && (
|
||||
<span className="absolute -top-0.5 -right-0.5 w-3.5 h-3.5 rounded-full bg-blue-600 text-white text-[9px] flex items-center justify-center font-bold">
|
||||
{activeFilters}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Search input */}
|
||||
<div className="flex-shrink-0 px-4 pt-3 pb-3 border-b border-neutral-200/70">
|
||||
<div className="flex items-center gap-1.5 mb-2">
|
||||
<div className="flex gap-2 mb-2">
|
||||
<div className="relative flex-1">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-neutral-400 pointer-events-none" />
|
||||
<input
|
||||
|
|
@ -255,28 +270,10 @@ export default function SearchCSDbNG({ config, setConfig, onClose }: SearchCSDbN
|
|||
<button
|
||||
onClick={() => doSearch(query)}
|
||||
disabled={isSearching || !query.trim()}
|
||||
className="p-1.5 rounded-lg hover:bg-neutral-100 disabled:opacity-40 transition-colors flex-shrink-0 text-neutral-400 hover:text-blue-600"
|
||||
title="Search"
|
||||
aria-label="Search"
|
||||
className="px-4 py-2.5 bg-blue-600 text-white rounded-xl text-sm font-medium hover:bg-blue-700 disabled:opacity-40 transition-colors"
|
||||
>
|
||||
{isSearching
|
||||
? <Loader2 className="w-5 h-5 animate-spin text-blue-500" />
|
||||
: <Search className="w-5 h-5" />}
|
||||
{isSearching ? <Loader2 className="w-4 h-4 animate-spin" /> : 'Search'}
|
||||
</button>
|
||||
{hasSearched && (
|
||||
<button
|
||||
onClick={() => setShowFilter(v => !v)}
|
||||
className={`relative p-1.5 rounded-lg transition-colors flex-shrink-0 ${showFilter ? 'bg-blue-100 text-blue-600' : 'hover:bg-neutral-100 text-neutral-400 hover:text-neutral-600'}`}
|
||||
title="Filter & sort results"
|
||||
>
|
||||
<SlidersHorizontal className="w-4 h-4" />
|
||||
{activeFilters > 0 && (
|
||||
<span className="absolute -top-0.5 -right-0.5 w-3.5 h-3.5 rounded-full bg-blue-600 text-white text-[9px] flex items-center justify-center font-bold">
|
||||
{activeFilters}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Type filter chips */}
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@ const _store = {
|
|||
offset: 0,
|
||||
hasMore: false,
|
||||
hasSearched: false,
|
||||
categoryFilter: null as number | null,
|
||||
scrollTop: 0,
|
||||
showFilter: false,
|
||||
filterText: '',
|
||||
|
|
@ -233,13 +234,7 @@ export default function SearchCommoServe({ config, setConfig, onClose }: SearchC
|
|||
}
|
||||
};
|
||||
|
||||
const handleSearch = () => {
|
||||
setFilterText('');
|
||||
const trimmed = query.trim();
|
||||
const q = trimmed && !trimmed.includes(':') ? `name:${trimmed}` : trimmed;
|
||||
if (q !== query) setQuery(q);
|
||||
doSearch(q, 0);
|
||||
};
|
||||
const handleSearch = () => { setFilterText(''); doSearch(query, 0); };
|
||||
const handleLoadMore = () => doSearch(query, offset, true);
|
||||
|
||||
const applyPreset = (group: PresetGroup, value: PresetValue) => {
|
||||
|
|
@ -361,11 +356,25 @@ export default function SearchCommoServe({ config, setConfig, onClose }: SearchC
|
|||
<img src={`${import.meta.env.BASE_URL}assets/favicon.cbm.png`} className="w-5 h-5 flex-shrink-0 object-contain" alt="" aria-hidden="true" />
|
||||
<span className="text-sm font-semibold text-neutral-700">CommoServe</span>
|
||||
</div>
|
||||
{hasSearched && (
|
||||
<button
|
||||
onClick={() => setShowFilter(v => !v)}
|
||||
className={`relative p-1.5 rounded-lg transition-colors ${showFilter ? 'bg-blue-100 text-blue-600' : 'hover:bg-neutral-100 text-neutral-400 hover:text-neutral-600'}`}
|
||||
title="Filter & sort results"
|
||||
>
|
||||
<SlidersHorizontal className="w-4 h-4" />
|
||||
{activeFilters > 0 && (
|
||||
<span className="absolute -top-0.5 -right-0.5 w-3.5 h-3.5 rounded-full bg-blue-600 text-white text-[9px] flex items-center justify-center font-bold">
|
||||
{activeFilters}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Search input + presets */}
|
||||
{/* Search input + presets + categories */}
|
||||
<div className="flex-shrink-0 px-4 pt-3 pb-3 border-b border-neutral-200/70">
|
||||
<div className="flex items-center gap-1.5 mb-2">
|
||||
<div className="flex gap-2 mb-2">
|
||||
<div className="relative flex-1">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-neutral-400 pointer-events-none" />
|
||||
<input
|
||||
|
|
@ -392,28 +401,10 @@ export default function SearchCommoServe({ config, setConfig, onClose }: SearchC
|
|||
<button
|
||||
onClick={handleSearch}
|
||||
disabled={isSearching}
|
||||
className="p-1.5 rounded-lg hover:bg-neutral-100 disabled:opacity-40 transition-colors flex-shrink-0 text-neutral-400 hover:text-blue-600"
|
||||
title="Search"
|
||||
aria-label="Search"
|
||||
className="px-4 py-2.5 bg-blue-600 text-white rounded-xl text-sm font-medium hover:bg-blue-700 disabled:opacity-40 transition-colors"
|
||||
>
|
||||
{isSearching
|
||||
? <Loader2 className="w-5 h-5 animate-spin text-blue-500" />
|
||||
: <Search className="w-5 h-5" />}
|
||||
{isSearching ? <Loader2 className="w-4 h-4 animate-spin" /> : 'Search'}
|
||||
</button>
|
||||
{hasSearched && (
|
||||
<button
|
||||
onClick={() => setShowFilter(v => !v)}
|
||||
className={`relative p-1.5 rounded-lg transition-colors flex-shrink-0 ${showFilter ? 'bg-blue-100 text-blue-600' : 'hover:bg-neutral-100 text-neutral-400 hover:text-neutral-600'}`}
|
||||
title="Filter & sort results"
|
||||
>
|
||||
<SlidersHorizontal className="w-4 h-4" />
|
||||
{activeFilters > 0 && (
|
||||
<span className="absolute -top-0.5 -right-0.5 w-3.5 h-3.5 rounded-full bg-blue-600 text-white text-[9px] flex items-center justify-center font-bold">
|
||||
{activeFilters}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{presets.length > 0 && (
|
||||
|
|
@ -530,7 +521,7 @@ export default function SearchCommoServe({ config, setConfig, onClose }: SearchC
|
|||
{item.handle && !item.group && (
|
||||
<span className="text-xs text-neutral-400">{item.handle}</span>
|
||||
)}
|
||||
{!!item.year && (
|
||||
{item.year && (
|
||||
<span className="text-xs text-neutral-400 flex items-center gap-0.5">
|
||||
<Calendar className="w-3 h-3" />{item.year}
|
||||
</span>
|
||||
|
|
@ -538,7 +529,7 @@ export default function SearchCommoServe({ config, setConfig, onClose }: SearchC
|
|||
{item.event && (
|
||||
<span className="text-xs text-neutral-400 truncate">{item.event}</span>
|
||||
)}
|
||||
{!!item.place && (
|
||||
{item.place && (
|
||||
<span className="text-xs text-neutral-400">#{item.place}</span>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -348,11 +348,31 @@ export default function SearchLocal({ config, setConfig, onClose, onOpenFolder }
|
|||
<img src={`${import.meta.env.BASE_URL}favicon.ico`} className="w-5 h-5 flex-shrink-0 object-contain" alt="" aria-hidden="true" />
|
||||
<span className="text-sm font-semibold text-neutral-700">Local</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-0.5">
|
||||
{dbPhase === 'ready' && !busy && (
|
||||
<button onClick={handleRefreshDb} className="p-1.5 rounded-lg hover:bg-neutral-100 text-neutral-400 hover:text-neutral-600 transition-colors" title="Reload database">
|
||||
<RefreshCw className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
<button onClick={() => setShowScanConfirm(true)} disabled={busy} className="p-1.5 rounded-lg hover:bg-neutral-100 text-neutral-400 hover:text-neutral-600 disabled:opacity-40 transition-colors" title="Scan /sd and rebuild database">
|
||||
<FolderSearch className="w-4 h-4" />
|
||||
</button>
|
||||
{hasSearched && (
|
||||
<button onClick={() => setShowFilter(v => !v)} className={`relative p-1.5 rounded-lg transition-colors ${showFilter ? 'bg-blue-100 text-blue-600' : 'hover:bg-neutral-100 text-neutral-400 hover:text-neutral-600'}`} title="Filter & sort">
|
||||
<SlidersHorizontal className="w-4 h-4" />
|
||||
{activeFilters > 0 && (
|
||||
<span className="absolute -top-0.5 -right-0.5 w-3.5 h-3.5 rounded-full bg-blue-600 text-white text-[9px] flex items-center justify-center font-bold">
|
||||
{activeFilters}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Search input */}
|
||||
<div className="flex-shrink-0 px-4 pt-3 pb-3 border-b border-neutral-100">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<div className="flex gap-2">
|
||||
<div className="relative flex-1">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-neutral-400 pointer-events-none" />
|
||||
<input
|
||||
|
|
@ -369,33 +389,12 @@ export default function SearchLocal({ config, setConfig, onClose, onOpenFolder }
|
|||
<button
|
||||
onClick={handleSearch}
|
||||
disabled={busy || !query.trim()}
|
||||
className="p-1.5 rounded-lg hover:bg-neutral-100 disabled:opacity-40 transition-colors flex-shrink-0 text-neutral-400 hover:text-blue-600"
|
||||
title="Search"
|
||||
aria-label="Search"
|
||||
className="px-4 py-2.5 bg-blue-600 text-white rounded-xl text-sm font-medium hover:bg-blue-700 disabled:opacity-40 transition-colors"
|
||||
>
|
||||
{busy
|
||||
? <Loader2 className="w-5 h-5 animate-spin text-blue-500" />
|
||||
: <Search className="w-5 h-5" />}
|
||||
Search
|
||||
</button>
|
||||
{dbPhase === 'ready' && !busy && (
|
||||
<button onClick={handleRefreshDb} className="p-1.5 rounded-lg hover:bg-neutral-100 text-neutral-400 hover:text-neutral-600 transition-colors flex-shrink-0" title="Reload database">
|
||||
<RefreshCw className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
<button onClick={() => setShowScanConfirm(true)} disabled={busy} className="p-1.5 rounded-lg hover:bg-neutral-100 text-neutral-400 hover:text-neutral-600 disabled:opacity-40 transition-colors flex-shrink-0" title="Scan /sd and rebuild database">
|
||||
<FolderSearch className="w-4 h-4" />
|
||||
</button>
|
||||
{hasSearched && (
|
||||
<button onClick={() => setShowFilter(v => !v)} className={`relative p-1.5 rounded-lg transition-colors flex-shrink-0 ${showFilter ? 'bg-blue-100 text-blue-600' : 'hover:bg-neutral-100 text-neutral-400 hover:text-neutral-600'}`} title="Filter & sort">
|
||||
<SlidersHorizontal className="w-4 h-4" />
|
||||
{activeFilters > 0 && (
|
||||
<span className="absolute -top-0.5 -right-0.5 w-3.5 h-3.5 rounded-full bg-blue-600 text-white text-[9px] flex items-center justify-center font-bold">
|
||||
{activeFilters}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* Filter + sort bar — same style as MediaManager */}
|
||||
|
|
|
|||
|
|
@ -974,12 +974,6 @@ class DAVRequestHandler(BaseHTTPRequestHandler):
|
|||
size = -1
|
||||
path, elem = self.path_elem_prev()
|
||||
ename = path[-1]
|
||||
if elem is None and len(path) > 1:
|
||||
# Parent directory doesn't exist — create it and re-resolve
|
||||
parent_parts = [p.rstrip('/') for p in path[:-1]]
|
||||
parent_fs = os.path.join(self.server.root.fsname, *parent_parts)
|
||||
os.makedirs(parent_fs, exist_ok=True)
|
||||
path, elem = self.path_elem_prev()
|
||||
except:
|
||||
self.send_response(400, 'Cannot parse request')
|
||||
self.send_header('Content-length', '0')
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user