@mdguggenbichler/slugbase-core 0.0.30 → 0.0.32
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/frontend/src/components/FilterChips.tsx +5 -3
- package/frontend/src/components/StatCard.tsx +82 -5
- package/frontend/src/components/bookmarks/BookmarkCard.tsx +317 -210
- package/frontend/src/components/bookmarks/BookmarkTableView.tsx +47 -23
- package/frontend/src/components/collections/CollectionToolbar.tsx +294 -0
- package/frontend/src/components/collections/README.md +44 -0
- package/frontend/src/components/collections/index.ts +2 -0
- package/frontend/src/components/dashboard/DashboardHeader.tsx +16 -0
- package/frontend/src/components/dashboard/MostUsedTagsSection.tsx +49 -0
- package/frontend/src/components/dashboard/PinnedSection.tsx +110 -0
- package/frontend/src/components/dashboard/QuickAccessSection.tsx +120 -0
- package/frontend/src/components/dashboard/README.md +35 -0
- package/frontend/src/components/dashboard/StatsCardsRow.tsx +78 -0
- package/frontend/src/components/dashboard/index.ts +17 -0
- package/frontend/src/config/docs.ts +19 -0
- package/frontend/src/config/mode.ts +6 -4
- package/frontend/src/locales/de.json +2 -0
- package/frontend/src/locales/en.json +1 -0
- package/frontend/src/locales/es.json +2 -0
- package/frontend/src/locales/fr.json +2 -0
- package/frontend/src/locales/it.json +2 -0
- package/frontend/src/locales/ja.json +2 -0
- package/frontend/src/locales/nl.json +2 -0
- package/frontend/src/locales/pl.json +2 -0
- package/frontend/src/locales/pt.json +2 -0
- package/frontend/src/locales/ru.json +2 -0
- package/frontend/src/locales/zh.json +2 -0
- package/frontend/src/pages/Bookmarks.tsx +97 -214
- package/frontend/src/pages/Dashboard.tsx +99 -216
- package/frontend/src/pages/Folders.tsx +181 -251
- package/frontend/src/pages/Profile.tsx +3 -2
- package/frontend/src/pages/Signup.tsx +3 -2
- package/frontend/src/pages/Tags.tsx +87 -145
- package/frontend/src/pages/admin/AdminLayout.tsx +2 -1
- package/package.json +1 -1
|
@@ -6,20 +6,18 @@ import api from '../api/client';
|
|
|
6
6
|
import ConfirmDialog from '../components/ui/ConfirmDialog';
|
|
7
7
|
import { useConfirmDialog } from '../hooks/useConfirmDialog';
|
|
8
8
|
import { useToast } from '../components/ui/Toast';
|
|
9
|
-
import { Plus,
|
|
9
|
+
import { Plus, Upload, Bookmark as BookmarkIcon, ExternalLink, FolderPlus, Tag as TagIcon, Share2, Trash2, Copy, ChevronLeft, ChevronRight } from 'lucide-react';
|
|
10
10
|
import BookmarkModal from '../components/modals/BookmarkModal';
|
|
11
11
|
import ImportModal from '../components/modals/ImportModal';
|
|
12
12
|
import ShareResourceDialog from '../components/sharing/ShareResourceDialog';
|
|
13
13
|
import Button from '../components/ui/Button';
|
|
14
|
-
import Select from '../components/ui/Select';
|
|
15
14
|
import BookmarkCard from '../components/bookmarks/BookmarkCard';
|
|
16
15
|
import BookmarkTableView from '../components/bookmarks/BookmarkTableView';
|
|
17
16
|
import { BulkMoveModal, BulkTagModal, BulkShareModal } from '../components/bookmarks/BulkActionModals';
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
17
|
+
import { type FilterKey } from '../components/bookmarks/FilterChips';
|
|
18
|
+
import { CollectionToolbar } from '../components/collections';
|
|
20
19
|
import { PageLoadingSkeleton } from '../components/ui/PageLoadingSkeleton';
|
|
21
20
|
import { Card } from '../components/ui/card';
|
|
22
|
-
import { PageHeader } from '../components/PageHeader';
|
|
23
21
|
import { useSidebar } from '../components/ui/sidebar';
|
|
24
22
|
import { useAppConfig } from '../contexts/AppConfigContext';
|
|
25
23
|
|
|
@@ -63,9 +61,6 @@ export default function Bookmarks() {
|
|
|
63
61
|
const saved = localStorage.getItem('bookmarks-view-mode');
|
|
64
62
|
return (saved === 'list' || saved === 'card') ? saved : 'card';
|
|
65
63
|
});
|
|
66
|
-
const [compactMode, setCompactMode] = useState(() => {
|
|
67
|
-
return localStorage.getItem('bookmarks-compact-mode') === 'true';
|
|
68
|
-
});
|
|
69
64
|
const [sortBy, setSortBy] = useState<SortOption>('recently_added');
|
|
70
65
|
const [selectedBookmarks, setSelectedBookmarks] = useState<Set<string>>(new Set());
|
|
71
66
|
const [allSelectedAcrossPages, setAllSelectedAcrossPages] = useState(false);
|
|
@@ -170,10 +165,6 @@ export default function Bookmarks() {
|
|
|
170
165
|
localStorage.setItem('bookmarks-view-mode', viewMode);
|
|
171
166
|
}, [viewMode]);
|
|
172
167
|
|
|
173
|
-
useEffect(() => {
|
|
174
|
-
localStorage.setItem('bookmarks-compact-mode', compactMode.toString());
|
|
175
|
-
}, [compactMode]);
|
|
176
|
-
|
|
177
168
|
useEffect(() => {
|
|
178
169
|
setSearchInputValue(searchQuery);
|
|
179
170
|
}, [searchQuery]);
|
|
@@ -499,202 +490,98 @@ export default function Bookmarks() {
|
|
|
499
490
|
|
|
500
491
|
return (
|
|
501
492
|
<div className="space-y-6 pb-24">
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
? t('bookmarks.showingXOfY', { x: displayedBookmarks.length, y: total })
|
|
510
|
-
: undefined
|
|
511
|
-
}
|
|
512
|
-
actions={
|
|
513
|
-
<div className="flex flex-wrap items-center gap-2">
|
|
514
|
-
<ScopeSegmentedControl
|
|
515
|
-
value={scope}
|
|
516
|
-
onChange={(s) => updateParams({ scope: s === 'all' ? undefined : s })}
|
|
517
|
-
options={[
|
|
518
|
-
{ value: 'all', label: t('bookmarks.scopeAll') },
|
|
519
|
-
{ value: 'mine', label: t('bookmarks.scopeMine') },
|
|
520
|
-
{ value: 'shared_with_me', label: t('common.scopeSharedWithMe') },
|
|
521
|
-
{ value: 'shared_by_me', label: t('common.scopeSharedByMe') },
|
|
522
|
-
]}
|
|
523
|
-
ariaLabel={t('bookmarks.scopeAll')}
|
|
524
|
-
/>
|
|
525
|
-
<Button
|
|
526
|
-
variant={pinnedFilter ? 'secondary' : 'ghost'}
|
|
527
|
-
size="sm"
|
|
528
|
-
icon={Pin}
|
|
529
|
-
onClick={() => updateParams({ pinned: pinnedFilter ? undefined : 'true' })}
|
|
530
|
-
title={t('bookmarks.pinned')}
|
|
531
|
-
aria-pressed={pinnedFilter}
|
|
532
|
-
>
|
|
533
|
-
<span className="hidden sm:inline">{t('bookmarks.pinned')}</span>
|
|
534
|
-
</Button>
|
|
535
|
-
<Button
|
|
536
|
-
variant="ghost"
|
|
537
|
-
size="sm"
|
|
538
|
-
icon={Upload}
|
|
539
|
-
onClick={() => setImportModalOpen(true)}
|
|
540
|
-
title={t('bookmarks.import')}
|
|
541
|
-
>
|
|
542
|
-
<span className="hidden sm:inline">{t('bookmarks.import')}</span>
|
|
543
|
-
</Button>
|
|
544
|
-
<Button
|
|
545
|
-
variant="ghost"
|
|
546
|
-
size="sm"
|
|
547
|
-
icon={Download}
|
|
548
|
-
onClick={handleExport}
|
|
549
|
-
title={t('bookmarks.export')}
|
|
550
|
-
>
|
|
551
|
-
<span className="hidden sm:inline">{t('bookmarks.export')}</span>
|
|
552
|
-
</Button>
|
|
553
|
-
<Button onClick={handleCreate} icon={Plus}>
|
|
554
|
-
{t('bookmarks.create')}
|
|
555
|
-
</Button>
|
|
556
|
-
</div>
|
|
493
|
+
<CollectionToolbar
|
|
494
|
+
title={t('bookmarks.title')}
|
|
495
|
+
count={total}
|
|
496
|
+
subtitle={
|
|
497
|
+
hasActiveFilters
|
|
498
|
+
? t('bookmarks.showingXOfY', { x: displayedBookmarks.length, y: total })
|
|
499
|
+
: undefined
|
|
557
500
|
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
{
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
</div>
|
|
643
|
-
|
|
644
|
-
{/* View Mode Toggle */}
|
|
645
|
-
<div className="flex items-center gap-2 border-l border-gray-200 dark:border-gray-700 pl-3">
|
|
646
|
-
<div className="flex items-center gap-1 bg-gray-100 dark:bg-gray-700 rounded-lg p-1">
|
|
647
|
-
<button
|
|
648
|
-
onClick={() => setViewMode('card')}
|
|
649
|
-
className={`p-1.5 rounded transition-colors ${
|
|
650
|
-
viewMode === 'card'
|
|
651
|
-
? 'bg-card text-primary shadow-sm'
|
|
652
|
-
: 'text-muted-foreground hover:text-foreground'
|
|
653
|
-
}`}
|
|
654
|
-
title={t('bookmarks.viewCard')}
|
|
655
|
-
>
|
|
656
|
-
<LayoutGrid className="h-4 w-4" />
|
|
657
|
-
</button>
|
|
658
|
-
<button
|
|
659
|
-
onClick={() => setViewMode('list')}
|
|
660
|
-
className={`p-1.5 rounded transition-colors ${
|
|
661
|
-
viewMode === 'list'
|
|
662
|
-
? 'bg-card text-primary shadow-sm'
|
|
663
|
-
: 'text-muted-foreground hover:text-foreground'
|
|
664
|
-
}`}
|
|
665
|
-
title={t('bookmarks.viewList')}
|
|
666
|
-
>
|
|
667
|
-
<List className="h-4 w-4" />
|
|
668
|
-
</button>
|
|
669
|
-
</div>
|
|
670
|
-
<button
|
|
671
|
-
onClick={() => setCompactMode(!compactMode)}
|
|
672
|
-
className={`px-3 py-1.5 text-sm rounded-lg transition-colors ${
|
|
673
|
-
compactMode
|
|
674
|
-
? 'bg-primary/20 text-primary'
|
|
675
|
-
: 'bg-muted text-muted-foreground hover:bg-accent'
|
|
676
|
-
}`}
|
|
677
|
-
title={t('bookmarks.compactMode')}
|
|
678
|
-
>
|
|
679
|
-
{t('bookmarks.compactMode')}
|
|
680
|
-
</button>
|
|
681
|
-
</div>
|
|
682
|
-
|
|
683
|
-
{/* Bulk Select Toggle */}
|
|
684
|
-
{!bulkMode && displayedBookmarks.length > 0 && (
|
|
685
|
-
<div className="flex items-center gap-2 border-l border-gray-200 dark:border-gray-700 pl-3">
|
|
686
|
-
<Button
|
|
687
|
-
variant="ghost"
|
|
688
|
-
size="sm"
|
|
689
|
-
icon={CheckSquare}
|
|
690
|
-
onClick={() => setBulkMode(true)}
|
|
691
|
-
>
|
|
692
|
-
{t('bookmarks.bulkSelect')}
|
|
693
|
-
</Button>
|
|
694
|
-
</div>
|
|
695
|
-
)}
|
|
696
|
-
</div>
|
|
697
|
-
</div>
|
|
501
|
+
tabs={{
|
|
502
|
+
value: scope,
|
|
503
|
+
onChange: (s) => updateParams({ scope: s === 'all' ? undefined : s }),
|
|
504
|
+
options: [
|
|
505
|
+
{ value: 'all', label: t('bookmarks.scopeAll') },
|
|
506
|
+
{ value: 'mine', label: t('bookmarks.scopeMine') },
|
|
507
|
+
{ value: 'shared_with_me', label: t('common.scopeSharedWithMe') },
|
|
508
|
+
{ value: 'shared_by_me', label: t('common.scopeSharedByMe') },
|
|
509
|
+
],
|
|
510
|
+
ariaLabel: t('bookmarks.scopeAll'),
|
|
511
|
+
}}
|
|
512
|
+
createButton={{ label: t('bookmarks.create'), onClick: handleCreate }}
|
|
513
|
+
filterChips={{
|
|
514
|
+
chips: filterChips,
|
|
515
|
+
onRemove: (key) => handleRemoveFilter(key as FilterKey),
|
|
516
|
+
onClearAll: handleResetFilters,
|
|
517
|
+
clearAllLabel: t('bookmarks.clearAllFilters'),
|
|
518
|
+
clearAllAriaLabel: t('bookmarks.clearAllFilters'),
|
|
519
|
+
}}
|
|
520
|
+
search={{
|
|
521
|
+
value: searchInputValue,
|
|
522
|
+
onChange: setSearchInputValue,
|
|
523
|
+
onSubmit: (value) => updateParams({ q: value || undefined }),
|
|
524
|
+
placeholder: t('common.searchPlaceholder'),
|
|
525
|
+
ariaLabel: t('common.searchPlaceholder'),
|
|
526
|
+
}}
|
|
527
|
+
folderFilter={{
|
|
528
|
+
value: selectedFolder || ALL_FILTER,
|
|
529
|
+
onChange: (value) => {
|
|
530
|
+
const params = new URLSearchParams(searchParams);
|
|
531
|
+
if (value && value !== ALL_FILTER) params.set('folder_id', value);
|
|
532
|
+
else params.delete('folder_id');
|
|
533
|
+
setSearchParams(params);
|
|
534
|
+
},
|
|
535
|
+
options: folderOptions,
|
|
536
|
+
placeholder: t('bookmarks.filterByFolder'),
|
|
537
|
+
}}
|
|
538
|
+
tagFilter={{
|
|
539
|
+
value: selectedTag || ALL_FILTER,
|
|
540
|
+
onChange: (value) => {
|
|
541
|
+
const params = new URLSearchParams(searchParams);
|
|
542
|
+
if (value && value !== ALL_FILTER) params.set('tag_id', value);
|
|
543
|
+
else params.delete('tag_id');
|
|
544
|
+
setSearchParams(params);
|
|
545
|
+
},
|
|
546
|
+
options: tagOptions,
|
|
547
|
+
placeholder: t('bookmarks.filterByTag'),
|
|
548
|
+
}}
|
|
549
|
+
sort={{
|
|
550
|
+
value: sortBy,
|
|
551
|
+
onChange: (value) => setSortBy(value as SortOption),
|
|
552
|
+
options: sortOptions,
|
|
553
|
+
className: 'min-w-[160px]',
|
|
554
|
+
}}
|
|
555
|
+
perPage={{
|
|
556
|
+
value: pageSize,
|
|
557
|
+
onChange: (value) => {
|
|
558
|
+
updateParams({ limit: String(value) });
|
|
559
|
+
setPage(0);
|
|
560
|
+
},
|
|
561
|
+
options: [...PAGE_SIZE_OPTIONS],
|
|
562
|
+
label: t('bookmarks.perPage'),
|
|
563
|
+
}}
|
|
564
|
+
viewMode={{
|
|
565
|
+
value: viewMode,
|
|
566
|
+
onChange: setViewMode,
|
|
567
|
+
cardLabel: t('bookmarks.viewCard'),
|
|
568
|
+
listLabel: t('bookmarks.viewList'),
|
|
569
|
+
}}
|
|
570
|
+
pinnedToggle={{
|
|
571
|
+
active: pinnedFilter,
|
|
572
|
+
onClick: () => updateParams({ pinned: pinnedFilter ? undefined : 'true' }),
|
|
573
|
+
label: t('bookmarks.pinned'),
|
|
574
|
+
}}
|
|
575
|
+
onImport={() => setImportModalOpen(true)}
|
|
576
|
+
importLabel={t('bookmarks.import')}
|
|
577
|
+
onExport={handleExport}
|
|
578
|
+
exportLabel={t('bookmarks.export')}
|
|
579
|
+
bulkSelect={
|
|
580
|
+
!bulkMode && displayedBookmarks.length > 0
|
|
581
|
+
? { onClick: () => setBulkMode(true), label: t('bookmarks.bulkSelect') }
|
|
582
|
+
: { onClick: () => setBulkMode(true), label: t('bookmarks.bulkSelect'), disabled: true }
|
|
583
|
+
}
|
|
584
|
+
/>
|
|
698
585
|
|
|
699
586
|
{/* Bulk Actions Bar - sticky bottom, visible when selecting */}
|
|
700
587
|
{bulkMode && (
|
|
@@ -836,16 +723,12 @@ export default function Bookmarks() {
|
|
|
836
723
|
</div>
|
|
837
724
|
</Card>
|
|
838
725
|
) : viewMode === 'card' ? (
|
|
839
|
-
<div className=
|
|
840
|
-
compactMode
|
|
841
|
-
? 'sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 2xl:grid-cols-8'
|
|
842
|
-
: 'sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6'
|
|
843
|
-
}`}>
|
|
726
|
+
<div className="grid gap-3 items-stretch [grid-template-columns:repeat(auto-fill,minmax(300px,1fr))]">
|
|
844
727
|
{displayedBookmarks.map((bookmark) => (
|
|
845
728
|
<BookmarkCard
|
|
846
729
|
key={bookmark.id}
|
|
847
730
|
bookmark={bookmark}
|
|
848
|
-
compact={
|
|
731
|
+
compact={false}
|
|
849
732
|
selected={selectedBookmarks.has(bookmark.id)}
|
|
850
733
|
onSelect={() => toggleSelectBookmark(bookmark.id)}
|
|
851
734
|
onEdit={() => handleEdit(bookmark)}
|
|
@@ -873,7 +756,7 @@ export default function Bookmarks() {
|
|
|
873
756
|
bulkMode={bulkMode}
|
|
874
757
|
user={user}
|
|
875
758
|
t={t}
|
|
876
|
-
compact={
|
|
759
|
+
compact={false}
|
|
877
760
|
/>
|
|
878
761
|
) : null}
|
|
879
762
|
|