@actuate-media/cms-admin 0.49.1 → 0.49.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/dist/AdminRoot.js +2 -2
- package/dist/AdminRoot.js.map +1 -1
- package/dist/actuate-admin.css +1 -1
- package/dist/components/CoauthorResultPopover.js +1 -1
- package/dist/components/CoauthorResultPopover.js.map +1 -1
- package/dist/components/CommentSidePanel.js +1 -1
- package/dist/components/CommentSidePanel.js.map +1 -1
- package/dist/components/ContentOverviewChart.js +1 -1
- package/dist/components/ContentOverviewChart.js.map +1 -1
- package/dist/components/FolderTree.js +19 -19
- package/dist/components/FolderTree.js.map +1 -1
- package/dist/components/NotificationBell.js +1 -1
- package/dist/components/NotificationBell.js.map +1 -1
- package/dist/components/PresenceChips.js +1 -1
- package/dist/components/PresenceChips.js.map +1 -1
- package/dist/components/SEOConfigPanel.js +2 -2
- package/dist/components/SEOConfigPanel.js.map +1 -1
- package/dist/components/SEOPanel.d.ts.map +1 -1
- package/dist/components/SEOPanel.js +2 -2
- package/dist/components/SEOPanel.js.map +1 -1
- package/dist/components/SEOPerformance.js +2 -2
- package/dist/components/SEOPerformance.js.map +1 -1
- package/dist/components/forms/EntriesTable.js +1 -1
- package/dist/components/forms/EntriesTable.js.map +1 -1
- package/dist/components/ui/Card.js +1 -1
- package/dist/components/ui/Card.js.map +1 -1
- package/dist/components/ui/EmptyState.js +1 -1
- package/dist/components/ui/EmptyState.js.map +1 -1
- package/dist/layout/Sidebar.js +1 -1
- package/dist/layout/Sidebar.js.map +1 -1
- package/dist/layout/primitives/PageContainer.js +1 -1
- package/dist/layout/primitives/PageContainer.js.map +1 -1
- package/dist/views/CollectionList.js +1 -1
- package/dist/views/CollectionList.js.map +1 -1
- package/dist/views/Dashboard.js +8 -8
- package/dist/views/Dashboard.js.map +1 -1
- package/dist/views/DocumentEdit.js +1 -1
- package/dist/views/DocumentEdit.js.map +1 -1
- package/dist/views/ForgotPassword.js +1 -1
- package/dist/views/ForgotPassword.js.map +1 -1
- package/dist/views/FormEditor.d.ts.map +1 -1
- package/dist/views/FormEditor.js +1 -1
- package/dist/views/FormEditor.js.map +1 -1
- package/dist/views/Login.js +1 -1
- package/dist/views/Login.js.map +1 -1
- package/dist/views/MediaBrowser.d.ts.map +1 -1
- package/dist/views/MediaBrowser.js +16 -16
- package/dist/views/MediaBrowser.js.map +1 -1
- package/dist/views/PageEditor.js +1 -1
- package/dist/views/PageEditor.js.map +1 -1
- package/dist/views/Pages/PagesListView.d.ts.map +1 -1
- package/dist/views/Pages/PagesListView.js +5 -5
- package/dist/views/Pages/PagesListView.js.map +1 -1
- package/dist/views/Pages.js +1 -1
- package/dist/views/Pages.js.map +1 -1
- package/dist/views/Posts/PostsListView.d.ts.map +1 -1
- package/dist/views/Posts/PostsListView.js +4 -4
- package/dist/views/Posts/PostsListView.js.map +1 -1
- package/dist/views/Redirects.d.ts.map +1 -1
- package/dist/views/Redirects.js +7 -7
- package/dist/views/Redirects.js.map +1 -1
- package/dist/views/ResetPassword.js +2 -2
- package/dist/views/ResetPassword.js.map +1 -1
- package/dist/views/ScriptTagEditor.js +4 -4
- package/dist/views/ScriptTagEditor.js.map +1 -1
- package/dist/views/ScriptTags.d.ts.map +1 -1
- package/dist/views/ScriptTags.js +5 -5
- package/dist/views/ScriptTags.js.map +1 -1
- package/dist/views/SetupWizard.js +2 -2
- package/dist/views/SetupWizard.js.map +1 -1
- package/dist/views/page-editor/SectionInspector.js +2 -2
- package/dist/views/page-editor/SectionInspector.js.map +1 -1
- package/dist/views/post-editor/PostHeader.d.ts.map +1 -1
- package/dist/views/post-editor/PostHeader.js +2 -2
- package/dist/views/post-editor/PostHeader.js.map +1 -1
- package/dist/views/post-editor/PostHeaderPanel.js +1 -1
- package/dist/views/post-editor/PostHeaderPanel.js.map +1 -1
- package/package.json +1 -1
- package/src/AdminRoot.tsx +2 -2
- package/src/components/CoauthorResultPopover.tsx +1 -1
- package/src/components/CommentSidePanel.tsx +1 -1
- package/src/components/ContentOverviewChart.tsx +1 -1
- package/src/components/FolderTree.tsx +32 -32
- package/src/components/NotificationBell.tsx +2 -2
- package/src/components/PresenceChips.tsx +1 -1
- package/src/components/SEOConfigPanel.tsx +3 -3
- package/src/components/SEOPanel.tsx +8 -6
- package/src/components/SEOPerformance.tsx +5 -5
- package/src/components/forms/EntriesTable.tsx +1 -1
- package/src/components/ui/Card.tsx +1 -1
- package/src/components/ui/EmptyState.tsx +1 -1
- package/src/layout/Sidebar.tsx +1 -1
- package/src/layout/primitives/PageContainer.tsx +1 -1
- package/src/views/CollectionList.tsx +1 -1
- package/src/views/Dashboard.tsx +12 -12
- package/src/views/DocumentEdit.tsx +4 -4
- package/src/views/ForgotPassword.tsx +1 -1
- package/src/views/FormEditor.tsx +1 -3
- package/src/views/Login.tsx +2 -2
- package/src/views/MediaBrowser.tsx +126 -120
- package/src/views/PageEditor.tsx +2 -2
- package/src/views/Pages/PagesListView.tsx +126 -118
- package/src/views/Pages.tsx +1 -1
- package/src/views/Posts/PostsListView.tsx +115 -111
- package/src/views/Redirects.tsx +48 -44
- package/src/views/ResetPassword.tsx +2 -2
- package/src/views/ScriptTagEditor.tsx +40 -40
- package/src/views/ScriptTags.tsx +34 -30
- package/src/views/SetupWizard.tsx +2 -2
- package/src/views/page-editor/SectionInspector.tsx +4 -4
- package/src/views/post-editor/PostHeader.tsx +2 -4
- package/src/views/post-editor/PostHeaderPanel.tsx +2 -2
|
@@ -361,7 +361,7 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
361
361
|
<button
|
|
362
362
|
type="button"
|
|
363
363
|
onClick={() => setSortConfig(toggleSort(sortConfig, sortKey))}
|
|
364
|
-
className="flex items-center gap-1 text-xs font-medium
|
|
364
|
+
className="text-foreground hover:text-foreground flex items-center gap-1 text-xs font-medium transition-colors"
|
|
365
365
|
>
|
|
366
366
|
{label}
|
|
367
367
|
{active ? (
|
|
@@ -371,7 +371,7 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
371
371
|
<ArrowDown className="h-3 w-3" />
|
|
372
372
|
)
|
|
373
373
|
) : (
|
|
374
|
-
<ArrowUpDown className="h-3 w-3
|
|
374
|
+
<ArrowUpDown className="text-muted-foreground h-3 w-3" />
|
|
375
375
|
)}
|
|
376
376
|
</button>
|
|
377
377
|
)
|
|
@@ -380,7 +380,7 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
380
380
|
if (loading) {
|
|
381
381
|
return (
|
|
382
382
|
<div className="flex h-64 items-center justify-center p-3 pr-6 sm:p-4 sm:pr-8">
|
|
383
|
-
<Loader2 className="h-6 w-6 animate-spin
|
|
383
|
+
<Loader2 className="text-primary h-6 w-6 animate-spin" />
|
|
384
384
|
</div>
|
|
385
385
|
)
|
|
386
386
|
}
|
|
@@ -388,12 +388,12 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
388
388
|
return (
|
|
389
389
|
<div className="flex h-full flex-col p-3 pr-6 sm:p-4 sm:pr-8">
|
|
390
390
|
{error && (
|
|
391
|
-
<div className="mb-4 flex items-center gap-3 rounded-lg border
|
|
392
|
-
<AlertTriangle className="h-5 w-5 shrink-0
|
|
393
|
-
<span className="flex-1 text-sm
|
|
391
|
+
<div className="border-destructive/30 bg-destructive/10 mb-4 flex items-center gap-3 rounded-lg border p-3">
|
|
392
|
+
<AlertTriangle className="text-destructive h-5 w-5 shrink-0" />
|
|
393
|
+
<span className="text-destructive flex-1 text-sm">{error}</span>
|
|
394
394
|
<button
|
|
395
395
|
onClick={refetch}
|
|
396
|
-
className="rounded-lg border
|
|
396
|
+
className="border-destructive/40 text-destructive hover:bg-destructive/15 rounded-lg border px-3 py-1 text-sm transition-colors"
|
|
397
397
|
>
|
|
398
398
|
Retry
|
|
399
399
|
</button>
|
|
@@ -405,14 +405,14 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
405
405
|
<button
|
|
406
406
|
type="button"
|
|
407
407
|
onClick={() => setSidebarOpen((prev) => !prev)}
|
|
408
|
-
className="rounded-lg p-1.5 transition-colors
|
|
408
|
+
className="hover:bg-muted rounded-lg p-1.5 transition-colors"
|
|
409
409
|
title={sidebarOpen ? 'Hide folders' : 'Show folders'}
|
|
410
410
|
>
|
|
411
|
-
<FolderInput className="h-5 w-5
|
|
411
|
+
<FolderInput className="text-muted-foreground h-5 w-5" />
|
|
412
412
|
</button>
|
|
413
413
|
<div>
|
|
414
|
-
<h1 className="mb-1 text-xl font-
|
|
415
|
-
<p className="text-
|
|
414
|
+
<h1 className="text-foreground mb-1 text-xl font-medium sm:text-2xl">Media Library</h1>
|
|
415
|
+
<p className="text-muted-foreground text-sm">{filteredAndSorted.length} files</p>
|
|
416
416
|
</div>
|
|
417
417
|
</div>
|
|
418
418
|
<div>
|
|
@@ -427,7 +427,7 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
427
427
|
<button
|
|
428
428
|
onClick={() => fileInputRef.current?.click()}
|
|
429
429
|
disabled={uploading}
|
|
430
|
-
className="flex items-center gap-2 rounded-lg
|
|
430
|
+
className="bg-primary text-primary-foreground hover:bg-primary/90 flex items-center gap-2 rounded-lg px-4 py-2 text-sm transition-colors disabled:opacity-50"
|
|
431
431
|
>
|
|
432
432
|
{uploading ? (
|
|
433
433
|
<Loader2 className="h-4 w-4 animate-spin" />
|
|
@@ -441,7 +441,7 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
441
441
|
|
|
442
442
|
<div className="flex min-h-0 flex-1 gap-4 overflow-hidden">
|
|
443
443
|
{sidebarOpen && (
|
|
444
|
-
<div className="flex w-56 shrink-0 flex-col overflow-hidden rounded-lg border
|
|
444
|
+
<div className="border-border bg-card flex w-56 shrink-0 flex-col overflow-hidden rounded-lg border">
|
|
445
445
|
<FolderTree
|
|
446
446
|
scope="media"
|
|
447
447
|
selected={folderSel}
|
|
@@ -457,23 +457,23 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
457
457
|
)}
|
|
458
458
|
|
|
459
459
|
<div className="flex min-w-0 flex-1 flex-col">
|
|
460
|
-
<div className="mb-4 rounded-lg border
|
|
460
|
+
<div className="border-border bg-card mb-4 rounded-lg border">
|
|
461
461
|
<div className="flex items-center justify-between p-3">
|
|
462
462
|
<div className="flex flex-1 items-center gap-3">
|
|
463
463
|
<div className="relative max-w-md flex-1">
|
|
464
|
-
<Search className="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2
|
|
464
|
+
<Search className="text-muted-foreground absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2" />
|
|
465
465
|
<input
|
|
466
466
|
type="text"
|
|
467
467
|
placeholder="Search media..."
|
|
468
468
|
value={searchQuery}
|
|
469
469
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
470
|
-
className="w-full rounded-lg border
|
|
470
|
+
className="border-input focus:ring-ring w-full rounded-lg border py-2 pr-3 pl-9 text-sm focus:ring-2 focus:outline-none"
|
|
471
471
|
/>
|
|
472
472
|
</div>
|
|
473
473
|
<select
|
|
474
474
|
value={filterType}
|
|
475
475
|
onChange={(e) => setFilterType(e.target.value)}
|
|
476
|
-
className="rounded-lg border
|
|
476
|
+
className="border-input focus:ring-ring rounded-lg border px-3 py-2 text-sm focus:ring-2 focus:outline-none"
|
|
477
477
|
>
|
|
478
478
|
<option value="all">All Types</option>
|
|
479
479
|
<option value="image">Images</option>
|
|
@@ -481,16 +481,16 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
481
481
|
<option value="document">Documents</option>
|
|
482
482
|
</select>
|
|
483
483
|
</div>
|
|
484
|
-
<div className="flex items-center gap-1 rounded-lg
|
|
484
|
+
<div className="bg-muted flex items-center gap-1 rounded-lg p-1">
|
|
485
485
|
<button
|
|
486
486
|
onClick={() => setViewMode('grid')}
|
|
487
|
-
className={`rounded p-1.5 transition-colors ${viewMode === 'grid' ? 'bg-
|
|
487
|
+
className={`rounded p-1.5 transition-colors ${viewMode === 'grid' ? 'bg-card text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground'}`}
|
|
488
488
|
>
|
|
489
489
|
<Grid3x3 className="h-4 w-4" />
|
|
490
490
|
</button>
|
|
491
491
|
<button
|
|
492
492
|
onClick={() => setViewMode('list')}
|
|
493
|
-
className={`rounded p-1.5 transition-colors ${viewMode === 'list' ? 'bg-
|
|
493
|
+
className={`rounded p-1.5 transition-colors ${viewMode === 'list' ? 'bg-card text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground'}`}
|
|
494
494
|
>
|
|
495
495
|
<List className="h-4 w-4" />
|
|
496
496
|
</button>
|
|
@@ -499,9 +499,9 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
499
499
|
</div>
|
|
500
500
|
|
|
501
501
|
{selectedMedia.length > 0 && (
|
|
502
|
-
<div className="mb-4 rounded-lg border
|
|
502
|
+
<div className="border-primary/30 bg-primary/10 mb-4 rounded-lg border p-3">
|
|
503
503
|
<div className="flex items-center justify-between">
|
|
504
|
-
<span className="text-
|
|
504
|
+
<span className="text-primary text-sm">
|
|
505
505
|
{selectedMedia.length} file{selectedMedia.length !== 1 ? 's' : ''} selected
|
|
506
506
|
</span>
|
|
507
507
|
<div className="flex items-center gap-2">
|
|
@@ -510,13 +510,13 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
510
510
|
for (const id of selectedMedia) await deleteMedia(id)
|
|
511
511
|
setSelectedMedia([])
|
|
512
512
|
}}
|
|
513
|
-
className="
|
|
513
|
+
className="bg-destructive text-destructive-foreground hover:bg-destructive/90 rounded-lg px-3 py-1.5 text-sm transition-colors"
|
|
514
514
|
>
|
|
515
515
|
Delete Selected
|
|
516
516
|
</button>
|
|
517
517
|
<button
|
|
518
518
|
onClick={() => setSelectedMedia([])}
|
|
519
|
-
className="
|
|
519
|
+
className="border-input bg-card hover:bg-muted rounded-lg border px-3 py-1.5 text-sm transition-colors"
|
|
520
520
|
>
|
|
521
521
|
Cancel
|
|
522
522
|
</button>
|
|
@@ -527,22 +527,24 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
527
527
|
|
|
528
528
|
{filteredAndSorted.length === 0 && !loading ? (
|
|
529
529
|
<div className="flex flex-col items-center justify-center py-16 text-center">
|
|
530
|
-
<div className="mb-4 flex h-12 w-12 items-center justify-center rounded-full
|
|
531
|
-
<ImageIcon className="h-6 w-6
|
|
530
|
+
<div className="bg-muted mb-4 flex h-12 w-12 items-center justify-center rounded-full">
|
|
531
|
+
<ImageIcon className="text-muted-foreground h-6 w-6" />
|
|
532
532
|
</div>
|
|
533
|
-
<h3 className="mb-1 text-sm font-medium
|
|
533
|
+
<h3 className="text-foreground mb-1 text-sm font-medium">
|
|
534
534
|
{folderSel.type === 'smart' && folderSel.smart === 'uncategorized'
|
|
535
535
|
? 'No uncategorized media'
|
|
536
536
|
: folderSel.type === 'folder'
|
|
537
537
|
? 'No media in this folder'
|
|
538
538
|
: 'No media yet'}
|
|
539
539
|
</h3>
|
|
540
|
-
<p className="text-
|
|
540
|
+
<p className="text-muted-foreground text-sm">
|
|
541
|
+
Upload your first file to get started.
|
|
542
|
+
</p>
|
|
541
543
|
</div>
|
|
542
544
|
) : (
|
|
543
545
|
<div className="flex min-h-0 flex-1 gap-4 overflow-hidden">
|
|
544
546
|
<div
|
|
545
|
-
className={`overflow-hidden rounded-lg border
|
|
547
|
+
className={`border-border bg-card overflow-hidden rounded-lg border transition-all duration-200 ${panelOpen ? 'min-w-0 flex-1' : 'w-full'}`}
|
|
546
548
|
>
|
|
547
549
|
{viewMode === 'grid' ? (
|
|
548
550
|
<div
|
|
@@ -567,25 +569,25 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
567
569
|
key={item.id}
|
|
568
570
|
className={`group relative flex cursor-pointer flex-col overflow-hidden rounded-lg border-2 transition-all ${
|
|
569
571
|
isActive
|
|
570
|
-
? 'border-
|
|
572
|
+
? 'border-primary ring-primary/30 ring-2'
|
|
571
573
|
: selectedMedia.includes(item.id)
|
|
572
|
-
? 'border-
|
|
573
|
-
: 'border-
|
|
574
|
+
? 'border-primary/60 ring-primary/20 ring-1'
|
|
575
|
+
: 'border-border hover:border-input'
|
|
574
576
|
}`}
|
|
575
577
|
onClick={() => openDetail(item)}
|
|
576
578
|
draggable
|
|
577
579
|
onDragStart={(e) => handleDragStart(e, item.id)}
|
|
578
580
|
>
|
|
579
581
|
<div className="relative aspect-square w-full overflow-hidden">
|
|
580
|
-
<div className="flex h-full w-full items-center justify-center
|
|
582
|
+
<div className="bg-muted flex h-full w-full items-center justify-center">
|
|
581
583
|
<MediaPreview item={item} />
|
|
582
584
|
</div>
|
|
583
585
|
{hasIssues && (
|
|
584
586
|
<div
|
|
585
|
-
className="absolute top-1.5 left-1.5 flex h-5 w-5 items-center justify-center rounded-full
|
|
587
|
+
className="bg-warning absolute top-1.5 left-1.5 flex h-5 w-5 items-center justify-center rounded-full"
|
|
586
588
|
title="Needs attention"
|
|
587
589
|
>
|
|
588
|
-
<AlertTriangle className="h-3 w-3
|
|
590
|
+
<AlertTriangle className="text-warning-foreground h-3 w-3" />
|
|
589
591
|
</div>
|
|
590
592
|
)}
|
|
591
593
|
<div
|
|
@@ -595,13 +597,13 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
595
597
|
<div
|
|
596
598
|
className={`flex h-5 w-5 items-center justify-center rounded border-2 transition-colors ${
|
|
597
599
|
selectedMedia.includes(item.id)
|
|
598
|
-
? 'border-
|
|
599
|
-
: 'border-
|
|
600
|
+
? 'border-primary bg-primary'
|
|
601
|
+
: 'border-input bg-card/80'
|
|
600
602
|
}`}
|
|
601
603
|
>
|
|
602
604
|
{selectedMedia.includes(item.id) && (
|
|
603
605
|
<svg
|
|
604
|
-
className="h-3 w-3
|
|
606
|
+
className="text-primary-foreground h-3 w-3"
|
|
605
607
|
fill="none"
|
|
606
608
|
viewBox="0 0 12 12"
|
|
607
609
|
>
|
|
@@ -617,18 +619,18 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
617
619
|
</div>
|
|
618
620
|
</div>
|
|
619
621
|
</div>
|
|
620
|
-
<div className="flex items-center gap-2 border-t
|
|
622
|
+
<div className="border-border bg-card flex items-center gap-2 border-t px-2 py-1.5">
|
|
621
623
|
<span
|
|
622
624
|
className={`mt-0.5 h-2 w-2 shrink-0 self-start rounded-full ${
|
|
623
|
-
hasIssues ? 'bg-
|
|
625
|
+
hasIssues ? 'bg-warning' : 'bg-success'
|
|
624
626
|
}`}
|
|
625
627
|
title={hasIssues ? 'Needs attention' : 'SEO complete'}
|
|
626
628
|
/>
|
|
627
629
|
<div className="min-w-0 flex-1">
|
|
628
|
-
<p className="truncate text-xs font-medium
|
|
630
|
+
<p className="text-foreground truncate text-xs font-medium">
|
|
629
631
|
{item.name}
|
|
630
632
|
</p>
|
|
631
|
-
<p className="truncate text-xs
|
|
633
|
+
<p className="text-muted-foreground truncate text-xs">
|
|
632
634
|
{[typeLabel, item.size].filter(Boolean).join(' · ')}
|
|
633
635
|
</p>
|
|
634
636
|
</div>
|
|
@@ -640,7 +642,7 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
640
642
|
) : (
|
|
641
643
|
<div className="h-full overflow-y-auto">
|
|
642
644
|
<table className="w-full">
|
|
643
|
-
<thead className="sticky top-0 border-b
|
|
645
|
+
<thead className="border-border bg-muted sticky top-0 border-b">
|
|
644
646
|
<tr>
|
|
645
647
|
<th className="w-8 px-3 py-2 text-left">
|
|
646
648
|
<input
|
|
@@ -650,7 +652,7 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
650
652
|
filteredAndSorted.length > 0
|
|
651
653
|
}
|
|
652
654
|
onChange={handleSelectAll}
|
|
653
|
-
className="
|
|
655
|
+
className="border-input rounded"
|
|
654
656
|
/>
|
|
655
657
|
</th>
|
|
656
658
|
<th className="w-6 px-1 py-2"></th>
|
|
@@ -666,19 +668,19 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
666
668
|
<th className="px-3 py-2 text-left">
|
|
667
669
|
<SortHeader label="Uploaded" sortKey="date" />
|
|
668
670
|
</th>
|
|
669
|
-
<th className="px-3 py-2 text-left text-xs font-medium
|
|
671
|
+
<th className="text-foreground px-3 py-2 text-left text-xs font-medium">
|
|
670
672
|
Status
|
|
671
673
|
</th>
|
|
672
674
|
</tr>
|
|
673
675
|
</thead>
|
|
674
|
-
<tbody className="divide-
|
|
676
|
+
<tbody className="divide-border divide-y">
|
|
675
677
|
{filteredAndSorted.map((item) => {
|
|
676
678
|
const isActive = activeItem?.id === item.id
|
|
677
679
|
const hasIssues = !item.altTag || !item.title || item.usedOn?.length === 0
|
|
678
680
|
return (
|
|
679
681
|
<tr
|
|
680
682
|
key={item.id}
|
|
681
|
-
className={`cursor-pointer transition-colors ${isActive ? 'bg-
|
|
683
|
+
className={`cursor-pointer transition-colors ${isActive ? 'bg-primary/10' : 'hover:bg-muted'}`}
|
|
682
684
|
onClick={() => openDetail(item)}
|
|
683
685
|
draggable
|
|
684
686
|
onDragStart={(e) => handleDragStart(e, item.id)}
|
|
@@ -693,34 +695,38 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
693
695
|
item.id,
|
|
694
696
|
)
|
|
695
697
|
}
|
|
696
|
-
className="
|
|
698
|
+
className="border-input rounded"
|
|
697
699
|
/>
|
|
698
700
|
</td>
|
|
699
701
|
<td className="cursor-grab px-1 py-2">
|
|
700
|
-
<GripVertical className="h-4 w-4
|
|
702
|
+
<GripVertical className="text-muted-foreground h-4 w-4" />
|
|
701
703
|
</td>
|
|
702
704
|
<td className="px-3 py-2">
|
|
703
705
|
<div className="flex items-center gap-3">
|
|
704
|
-
<div className="flex h-10 w-10 items-center justify-center overflow-hidden rounded
|
|
706
|
+
<div className="bg-muted flex h-10 w-10 items-center justify-center overflow-hidden rounded">
|
|
705
707
|
<MediaPreview item={item} />
|
|
706
708
|
</div>
|
|
707
|
-
<span className="text-sm font-medium
|
|
709
|
+
<span className="text-foreground text-sm font-medium">
|
|
708
710
|
{item.name}
|
|
709
711
|
</span>
|
|
710
712
|
</div>
|
|
711
713
|
</td>
|
|
712
|
-
<td className="px-3 py-2 text-sm
|
|
714
|
+
<td className="text-muted-foreground px-3 py-2 text-sm">
|
|
713
715
|
{item.format ?? item.type}
|
|
714
716
|
</td>
|
|
715
|
-
<td className="px-3 py-2 text-sm
|
|
716
|
-
|
|
717
|
+
<td className="text-muted-foreground px-3 py-2 text-sm">
|
|
718
|
+
{item.size}
|
|
719
|
+
</td>
|
|
720
|
+
<td className="text-muted-foreground px-3 py-2 text-sm">
|
|
721
|
+
{item.date}
|
|
722
|
+
</td>
|
|
717
723
|
<td className="px-3 py-2">
|
|
718
724
|
{hasIssues ? (
|
|
719
|
-
<span className="inline-flex items-center gap-1 rounded-full
|
|
725
|
+
<span className="bg-warning/15 text-warning inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium">
|
|
720
726
|
<AlertTriangle className="h-3 w-3" /> Needs attention
|
|
721
727
|
</span>
|
|
722
728
|
) : (
|
|
723
|
-
<span className="inline-flex items-center rounded-full
|
|
729
|
+
<span className="bg-success/15 text-success inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium">
|
|
724
730
|
Complete
|
|
725
731
|
</span>
|
|
726
732
|
)}
|
|
@@ -735,42 +741,42 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
735
741
|
</div>
|
|
736
742
|
|
|
737
743
|
{panelOpen && activeItem && (
|
|
738
|
-
<div className="flex w-80 shrink-0 flex-col overflow-y-auto rounded-lg border
|
|
739
|
-
<div className="sticky top-0 z-10 flex items-center justify-between border-b
|
|
740
|
-
<h3 className="truncate text-sm font-
|
|
744
|
+
<div className="border-border bg-card flex w-80 shrink-0 flex-col overflow-y-auto rounded-lg border lg:w-96">
|
|
745
|
+
<div className="border-border bg-card sticky top-0 z-10 flex items-center justify-between border-b p-4">
|
|
746
|
+
<h3 className="text-foreground truncate text-sm font-medium">
|
|
741
747
|
{activeItem.name}
|
|
742
748
|
</h3>
|
|
743
749
|
<button
|
|
744
750
|
onClick={closeDetail}
|
|
745
|
-
className="rounded p-1 transition-colors
|
|
751
|
+
className="hover:bg-muted rounded p-1 transition-colors"
|
|
746
752
|
aria-label="Close panel"
|
|
747
753
|
>
|
|
748
|
-
<X className="h-4 w-4
|
|
754
|
+
<X className="text-muted-foreground h-4 w-4" />
|
|
749
755
|
</button>
|
|
750
756
|
</div>
|
|
751
757
|
|
|
752
758
|
<div className="flex-1 overflow-y-auto">
|
|
753
|
-
<div className="border-
|
|
754
|
-
<div className="flex aspect-video items-center justify-center overflow-hidden rounded-lg
|
|
759
|
+
<div className="border-border border-b p-4">
|
|
760
|
+
<div className="bg-muted flex aspect-video items-center justify-center overflow-hidden rounded-lg">
|
|
755
761
|
{isImageMedia(activeItem) ? (
|
|
756
762
|
<MediaPreview item={activeItem} />
|
|
757
763
|
) : (
|
|
758
|
-
<ImageIcon className="h-12 w-12
|
|
764
|
+
<ImageIcon className="text-muted-foreground h-12 w-12" />
|
|
759
765
|
)}
|
|
760
766
|
</div>
|
|
761
767
|
</div>
|
|
762
768
|
|
|
763
769
|
{issues.length > 0 && (
|
|
764
|
-
<div className="mx-4 mt-4 rounded-lg border
|
|
770
|
+
<div className="border-warning/30 bg-warning/10 mx-4 mt-4 rounded-lg border p-3">
|
|
765
771
|
<div className="flex items-start gap-2">
|
|
766
|
-
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0
|
|
772
|
+
<AlertTriangle className="text-warning mt-0.5 h-4 w-4 shrink-0" />
|
|
767
773
|
<div>
|
|
768
|
-
<p className="mb-1 text-xs font-
|
|
774
|
+
<p className="text-warning mb-1 text-xs font-medium">
|
|
769
775
|
{issues.length} issue{issues.length !== 1 ? 's' : ''} found
|
|
770
776
|
</p>
|
|
771
777
|
<ul className="space-y-0.5">
|
|
772
778
|
{issues.map((issue, i) => (
|
|
773
|
-
<li key={i} className="text-
|
|
779
|
+
<li key={i} className="text-warning text-xs">
|
|
774
780
|
• {issue}
|
|
775
781
|
</li>
|
|
776
782
|
))}
|
|
@@ -784,7 +790,7 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
784
790
|
if (!activeItem.title) await handleAiGenerate('title')
|
|
785
791
|
if (activeItem.sizeBytes > 2000000) await handleAiGenerate('optimize')
|
|
786
792
|
}}
|
|
787
|
-
className="mt-2 flex w-full items-center justify-center gap-1.5 rounded-lg
|
|
793
|
+
className="bg-warning text-warning-foreground hover:bg-warning/90 mt-2 flex w-full items-center justify-center gap-1.5 rounded-lg px-3 py-1.5 text-xs transition-colors"
|
|
788
794
|
>
|
|
789
795
|
<Sparkles className="h-3.5 w-3.5" />
|
|
790
796
|
AI Fix All Issues
|
|
@@ -792,68 +798,68 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
792
798
|
</div>
|
|
793
799
|
)}
|
|
794
800
|
|
|
795
|
-
<div className="space-y-3 border-b
|
|
796
|
-
<h4 className="text-xs font-
|
|
801
|
+
<div className="border-border space-y-3 border-b p-4">
|
|
802
|
+
<h4 className="text-muted-foreground text-xs font-medium tracking-wide uppercase">
|
|
797
803
|
File Information
|
|
798
804
|
</h4>
|
|
799
805
|
<div className="grid grid-cols-2 gap-3">
|
|
800
806
|
<div>
|
|
801
|
-
<div className="mb-0.5 text-xs
|
|
802
|
-
<div className="text-
|
|
807
|
+
<div className="text-muted-foreground mb-0.5 text-xs">Format</div>
|
|
808
|
+
<div className="text-foreground text-sm">
|
|
803
809
|
{activeItem.format ?? 'Unknown'}
|
|
804
810
|
</div>
|
|
805
811
|
</div>
|
|
806
812
|
<div>
|
|
807
|
-
<div className="mb-0.5 text-xs
|
|
808
|
-
<div className="flex items-center gap-1 text-sm
|
|
813
|
+
<div className="text-muted-foreground mb-0.5 text-xs">File Size</div>
|
|
814
|
+
<div className="text-foreground flex items-center gap-1 text-sm">
|
|
809
815
|
{activeItem.size}
|
|
810
816
|
{activeItem.sizeBytes > 2000000 && (
|
|
811
|
-
<span className="text-
|
|
817
|
+
<span className="text-warning text-xs">(large)</span>
|
|
812
818
|
)}
|
|
813
819
|
</div>
|
|
814
820
|
</div>
|
|
815
821
|
<div>
|
|
816
|
-
<div className="mb-0.5 text-xs
|
|
817
|
-
<div className="text-
|
|
822
|
+
<div className="text-muted-foreground mb-0.5 text-xs">Dimensions</div>
|
|
823
|
+
<div className="text-foreground text-sm">
|
|
818
824
|
{activeItem.dimensions ?? '—'}
|
|
819
825
|
</div>
|
|
820
826
|
</div>
|
|
821
827
|
<div>
|
|
822
|
-
<div className="mb-0.5 text-xs
|
|
823
|
-
<div className="text-
|
|
828
|
+
<div className="text-muted-foreground mb-0.5 text-xs">Uploaded</div>
|
|
829
|
+
<div className="text-foreground text-sm">{activeItem.date}</div>
|
|
824
830
|
</div>
|
|
825
831
|
</div>
|
|
826
832
|
|
|
827
833
|
<div>
|
|
828
|
-
<div className="mb-1 text-xs
|
|
834
|
+
<div className="text-muted-foreground mb-1 text-xs">URL</div>
|
|
829
835
|
<div className="flex items-center gap-1">
|
|
830
|
-
<code className="flex-1 truncate rounded border
|
|
836
|
+
<code className="border-border bg-muted text-foreground flex-1 truncate rounded border px-2 py-1.5 text-xs">
|
|
831
837
|
{activeItem.url}
|
|
832
838
|
</code>
|
|
833
839
|
<button
|
|
834
840
|
onClick={handleCopyUrl}
|
|
835
|
-
className="shrink-0 rounded p-1.5 transition-colors
|
|
841
|
+
className="hover:bg-muted shrink-0 rounded p-1.5 transition-colors"
|
|
836
842
|
title="Copy URL"
|
|
837
843
|
>
|
|
838
|
-
<Copy className="h-3.5 w-3.5
|
|
844
|
+
<Copy className="text-muted-foreground h-3.5 w-3.5" />
|
|
839
845
|
</button>
|
|
840
846
|
</div>
|
|
841
847
|
</div>
|
|
842
848
|
</div>
|
|
843
849
|
|
|
844
|
-
<div className="space-y-4 border-b
|
|
845
|
-
<h4 className="text-xs font-
|
|
850
|
+
<div className="border-border space-y-4 border-b p-4">
|
|
851
|
+
<h4 className="text-muted-foreground text-xs font-medium tracking-wide uppercase">
|
|
846
852
|
SEO & Accessibility
|
|
847
853
|
</h4>
|
|
848
854
|
|
|
849
855
|
<div>
|
|
850
856
|
<div className="mb-1 flex items-center justify-between">
|
|
851
|
-
<label className="text-sm font-medium
|
|
857
|
+
<label className="text-foreground text-sm font-medium">Alt Tag</label>
|
|
852
858
|
<button
|
|
853
859
|
type="button"
|
|
854
860
|
onClick={() => handleAiGenerate('alt')}
|
|
855
861
|
disabled={aiGenerating === 'alt'}
|
|
856
|
-
className="flex items-center gap-1 text-xs
|
|
862
|
+
className="text-primary hover:text-primary/80 flex items-center gap-1 text-xs transition-colors disabled:opacity-50"
|
|
857
863
|
>
|
|
858
864
|
<Bot className="h-3.5 w-3.5" />
|
|
859
865
|
{aiGenerating === 'alt' ? 'Generating...' : 'AI Generate'}
|
|
@@ -864,10 +870,10 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
864
870
|
onChange={(e) => setEditAlt(e.target.value)}
|
|
865
871
|
placeholder="Describe this image for accessibility..."
|
|
866
872
|
rows={2}
|
|
867
|
-
className="w-full resize-none rounded-lg border
|
|
873
|
+
className="border-input focus:ring-ring w-full resize-none rounded-lg border px-3 py-2 text-sm focus:ring-2 focus:outline-none"
|
|
868
874
|
/>
|
|
869
875
|
{!editAlt && (
|
|
870
|
-
<p className="mt-1 text-xs
|
|
876
|
+
<p className="text-destructive mt-1 text-xs">
|
|
871
877
|
Required for accessibility and SEO
|
|
872
878
|
</p>
|
|
873
879
|
)}
|
|
@@ -875,12 +881,12 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
875
881
|
|
|
876
882
|
<div>
|
|
877
883
|
<div className="mb-1 flex items-center justify-between">
|
|
878
|
-
<label className="text-sm font-medium
|
|
884
|
+
<label className="text-foreground text-sm font-medium">Title</label>
|
|
879
885
|
<button
|
|
880
886
|
type="button"
|
|
881
887
|
onClick={() => handleAiGenerate('title')}
|
|
882
888
|
disabled={aiGenerating === 'title'}
|
|
883
|
-
className="flex items-center gap-1 text-xs
|
|
889
|
+
className="text-primary hover:text-primary/80 flex items-center gap-1 text-xs transition-colors disabled:opacity-50"
|
|
884
890
|
>
|
|
885
891
|
<Bot className="h-3.5 w-3.5" />
|
|
886
892
|
{aiGenerating === 'title' ? 'Generating...' : 'AI Generate'}
|
|
@@ -891,25 +897,25 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
891
897
|
value={editTitle}
|
|
892
898
|
onChange={(e) => setEditTitle(e.target.value)}
|
|
893
899
|
placeholder="Image title..."
|
|
894
|
-
className="w-full rounded-lg border
|
|
900
|
+
className="border-input focus:ring-ring w-full rounded-lg border px-3 py-2 text-sm focus:ring-2 focus:outline-none"
|
|
895
901
|
/>
|
|
896
902
|
</div>
|
|
897
903
|
|
|
898
904
|
<div>
|
|
899
|
-
<label className="mb-1 block text-sm font-medium
|
|
905
|
+
<label className="text-foreground mb-1 block text-sm font-medium">
|
|
900
906
|
File Name
|
|
901
907
|
</label>
|
|
902
908
|
<input
|
|
903
909
|
type="text"
|
|
904
910
|
value={editFilename}
|
|
905
911
|
onChange={(e) => setEditFilename(e.target.value)}
|
|
906
|
-
className="w-full rounded-lg border
|
|
912
|
+
className="border-input focus:ring-ring w-full rounded-lg border px-3 py-2 text-sm focus:ring-2 focus:outline-none"
|
|
907
913
|
/>
|
|
908
914
|
</div>
|
|
909
915
|
</div>
|
|
910
916
|
|
|
911
|
-
<div className="border-
|
|
912
|
-
<h4 className="mb-3 text-xs font-
|
|
917
|
+
<div className="border-border border-b p-4">
|
|
918
|
+
<h4 className="text-muted-foreground mb-3 text-xs font-medium tracking-wide uppercase">
|
|
913
919
|
Used On {activeItem.usedOn && `(${activeItem.usedOn.length})`}
|
|
914
920
|
</h4>
|
|
915
921
|
{activeItem.usedOn && activeItem.usedOn.length > 0 ? (
|
|
@@ -919,23 +925,23 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
919
925
|
key={i}
|
|
920
926
|
type="button"
|
|
921
927
|
onClick={() => onNavigate?.(usage.path)}
|
|
922
|
-
className="flex w-full items-center gap-2 rounded-lg border
|
|
928
|
+
className="border-border hover:bg-muted flex w-full items-center gap-2 rounded-lg border p-2 text-left transition-colors"
|
|
923
929
|
>
|
|
924
|
-
<Link2 className="h-4 w-4 shrink-0
|
|
925
|
-
<span className="flex-1 truncate text-sm
|
|
930
|
+
<Link2 className="text-muted-foreground h-4 w-4 shrink-0" />
|
|
931
|
+
<span className="text-foreground flex-1 truncate text-sm">
|
|
926
932
|
{usage.page}
|
|
927
933
|
</span>
|
|
928
|
-
<ExternalLink className="h-3.5 w-3.5 shrink-0
|
|
934
|
+
<ExternalLink className="text-muted-foreground h-3.5 w-3.5 shrink-0" />
|
|
929
935
|
</button>
|
|
930
936
|
))}
|
|
931
937
|
</div>
|
|
932
938
|
) : (
|
|
933
|
-
<div className="
|
|
939
|
+
<div className="border-warning/30 bg-warning/10 rounded-lg border p-3">
|
|
934
940
|
<div className="flex items-start gap-2">
|
|
935
|
-
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0
|
|
941
|
+
<AlertTriangle className="text-warning mt-0.5 h-4 w-4 shrink-0" />
|
|
936
942
|
<div>
|
|
937
|
-
<p className="text-xs font-medium
|
|
938
|
-
<p className="mt-0.5 text-xs
|
|
943
|
+
<p className="text-warning text-xs font-medium">Orphaned media</p>
|
|
944
|
+
<p className="text-warning mt-0.5 text-xs">
|
|
939
945
|
This file isn't used on any page. Consider deleting it to save
|
|
940
946
|
storage.
|
|
941
947
|
</p>
|
|
@@ -946,37 +952,37 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
946
952
|
</div>
|
|
947
953
|
|
|
948
954
|
<div className="space-y-3 p-4">
|
|
949
|
-
<h4 className="text-xs font-
|
|
955
|
+
<h4 className="text-muted-foreground text-xs font-medium tracking-wide uppercase">
|
|
950
956
|
AI Optimization
|
|
951
957
|
</h4>
|
|
952
958
|
<button
|
|
953
959
|
type="button"
|
|
954
960
|
onClick={() => handleAiGenerate('optimize')}
|
|
955
961
|
disabled={aiGenerating === 'optimize'}
|
|
956
|
-
className="flex w-full items-center gap-2 rounded-lg border
|
|
962
|
+
className="border-primary/30 bg-primary/10 hover:bg-primary/15 flex w-full items-center gap-2 rounded-lg border p-3 text-left transition-colors disabled:opacity-50"
|
|
957
963
|
>
|
|
958
964
|
<Sparkles
|
|
959
|
-
className={`h-5 w-5 shrink-0
|
|
965
|
+
className={`text-primary h-5 w-5 shrink-0 ${aiGenerating === 'optimize' ? 'animate-spin' : ''}`}
|
|
960
966
|
/>
|
|
961
967
|
<div className="flex-1">
|
|
962
|
-
<div className="text-sm font-medium
|
|
968
|
+
<div className="text-primary text-sm font-medium">
|
|
963
969
|
{aiGenerating === 'optimize' ? 'Optimizing...' : 'Optimize Image'}
|
|
964
970
|
</div>
|
|
965
|
-
<div className="mt-0.5 text-xs
|
|
971
|
+
<div className="text-primary mt-0.5 text-xs">
|
|
966
972
|
Compress and convert to modern format (WebP/AVIF)
|
|
967
973
|
</div>
|
|
968
974
|
</div>
|
|
969
975
|
</button>
|
|
970
976
|
<button
|
|
971
977
|
type="button"
|
|
972
|
-
className="flex w-full items-center gap-2 rounded-lg border
|
|
978
|
+
className="border-border hover:bg-muted flex w-full items-center gap-2 rounded-lg border p-3 text-left transition-colors"
|
|
973
979
|
>
|
|
974
|
-
<Bot className="h-5 w-5 shrink-0
|
|
980
|
+
<Bot className="text-muted-foreground h-5 w-5 shrink-0" />
|
|
975
981
|
<div className="flex-1">
|
|
976
|
-
<div className="text-sm font-medium
|
|
982
|
+
<div className="text-foreground text-sm font-medium">
|
|
977
983
|
AI Content Analysis
|
|
978
984
|
</div>
|
|
979
|
-
<div className="mt-0.5 text-xs
|
|
985
|
+
<div className="text-muted-foreground mt-0.5 text-xs">
|
|
980
986
|
Detect objects, faces, text, and suggest categories
|
|
981
987
|
</div>
|
|
982
988
|
</div>
|
|
@@ -984,30 +990,30 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
984
990
|
</div>
|
|
985
991
|
</div>
|
|
986
992
|
|
|
987
|
-
<div className="sticky bottom-0 flex items-center gap-2 border-t
|
|
993
|
+
<div className="border-border bg-card sticky bottom-0 flex items-center gap-2 border-t p-4">
|
|
988
994
|
<button
|
|
989
995
|
type="button"
|
|
990
996
|
onClick={handleSaveDetails}
|
|
991
997
|
disabled={saving}
|
|
992
|
-
className="flex flex-1 items-center justify-center gap-2 rounded-lg
|
|
998
|
+
className="bg-primary text-primary-foreground hover:bg-primary/90 flex flex-1 items-center justify-center gap-2 rounded-lg px-4 py-2 text-sm transition-colors disabled:opacity-50"
|
|
993
999
|
>
|
|
994
1000
|
{saving && <Loader2 className="h-4 w-4 animate-spin" />}
|
|
995
1001
|
{saving ? 'Saving...' : 'Save Changes'}
|
|
996
1002
|
</button>
|
|
997
1003
|
<button
|
|
998
1004
|
type="button"
|
|
999
|
-
className="rounded-lg p-2 transition-colors
|
|
1005
|
+
className="hover:bg-muted rounded-lg p-2 transition-colors"
|
|
1000
1006
|
title="Download"
|
|
1001
1007
|
>
|
|
1002
|
-
<Download className="h-4 w-4
|
|
1008
|
+
<Download className="text-muted-foreground h-4 w-4" />
|
|
1003
1009
|
</button>
|
|
1004
1010
|
<button
|
|
1005
1011
|
type="button"
|
|
1006
1012
|
onClick={() => deleteMedia(activeItem.id)}
|
|
1007
|
-
className="rounded-lg p-2 transition-colors
|
|
1013
|
+
className="hover:bg-muted rounded-lg p-2 transition-colors"
|
|
1008
1014
|
title="Delete"
|
|
1009
1015
|
>
|
|
1010
|
-
<Trash2 className="h-4 w-4
|
|
1016
|
+
<Trash2 className="text-destructive h-4 w-4" />
|
|
1011
1017
|
</button>
|
|
1012
1018
|
</div>
|
|
1013
1019
|
</div>
|