···
105
105
}
106
106
107
107
.collections-filter {
108
108
-
display: flex;
109
109
-
flex-wrap: wrap;
110
110
-
gap: 10px;
111
111
-
max-height: 200px;
108
108
+
max-height: 300px;
112
109
overflow-y: auto;
113
110
padding: 10px 0;
111
111
+
background-color: var(--background);
112
112
+
border-radius: 8px;
113
113
+
border: 1px solid var(--card-border);
114
114
+
margin-top: 10px;
114
115
}
115
116
116
116
-
.collection-checkbox {
117
117
+
.collection-item {
118
118
+
padding: 8px 12px;
119
119
+
border-bottom: 1px solid var(--card-border);
120
120
+
cursor: pointer;
117
121
display: flex;
118
122
align-items: center;
119
119
-
margin-right: 15px;
120
120
-
margin-bottom: 5px;
123
123
+
transition: background-color 0.2s;
121
124
}
122
125
123
123
-
.collection-checkbox input[type="checkbox"] {
124
124
-
margin-right: 5px;
126
126
+
.collection-item:last-child {
127
127
+
border-bottom: none;
125
128
}
126
129
127
127
-
.collection-checkbox label {
128
128
-
font-size: 0.9rem;
130
130
+
.collection-item:hover {
131
131
+
background-color: rgba(0, 0, 0, 0.05);
132
132
+
}
133
133
+
134
134
+
.collection-item.selected {
135
135
+
background-color: rgba(var(--button-bg-rgb), 0.1);
136
136
+
}
137
137
+
138
138
+
.collection-item-name {
139
139
+
font-size: 0.95rem;
129
140
color: var(--text);
130
130
-
cursor: pointer;
141
141
+
flex-grow: 1;
142
142
+
}
143
143
+
144
144
+
.collection-item-checkbox {
145
145
+
margin-right: 10px;
146
146
+
width: 16px;
147
147
+
height: 16px;
148
148
+
accent-color: var(--button-bg);
149
149
+
}
150
150
+
151
151
+
.dark-mode .collection-item:hover {
152
152
+
background-color: rgba(255, 255, 255, 0.05);
153
153
+
}
154
154
+
155
155
+
.dark-mode .collection-item.selected {
156
156
+
background-color: rgba(var(--button-bg-rgb), 0.2);
131
157
}
132
158
133
159
.refresh-button {
···
106
106
107
107
// Fetch records for each collection in parallel
108
108
const fetchPromises = collectionsList.map(async (collection) => {
109
109
-
let url = `${endpoint}/xrpc/com.atproto.repo.listRecords?repo=${encodeURIComponent(userDid)}&collection=${encodeURIComponent(collection)}&limit=10`;
109
109
+
// Limit to 5 records per collection initially (for 20 records total across ~4 collections)
110
110
+
let url = `${endpoint}/xrpc/com.atproto.repo.listRecords?repo=${encodeURIComponent(userDid)}&collection=${encodeURIComponent(collection)}&limit=5`;
110
111
111
112
// Add cursor if loading more and we have a cursor for this collection
112
113
if (isLoadMore && newCursors[collection]) {
···
155
156
return new Date(b.timestamp) - new Date(a.timestamp);
156
157
});
157
158
159
159
+
// If not loading more, limit to 20 most recent records
160
160
+
if (!isLoadMore) {
161
161
+
allRecords = allRecords.slice(0, 20);
162
162
+
}
163
163
+
158
164
setRecords(allRecords);
159
165
setCollectionCursors(newCursors);
160
166
setFetchingMore(false);
···
165
171
}
166
172
};
167
173
168
168
-
// Handle filter change
169
169
-
const handleFilterChange = (e) => {
170
170
-
const { value, checked } = e.target;
171
171
-
172
172
-
if (checked) {
173
173
-
setSelectedCollections(prev => [...prev, value]);
174
174
+
// Toggle collection selection
175
175
+
const toggleCollection = (collection) => {
176
176
+
if (selectedCollections.includes(collection)) {
177
177
+
setSelectedCollections(prev => prev.filter(item => item !== collection));
174
178
} else {
175
175
-
setSelectedCollections(prev => prev.filter(item => item !== value));
179
179
+
setSelectedCollections(prev => [...prev, collection]);
176
180
}
177
181
};
178
182
···
294
298
295
299
<div className="collections-filter">
296
300
{collections.map(collection => (
297
297
-
<div key={collection} className="collection-checkbox">
301
301
+
<div
302
302
+
key={collection}
303
303
+
className={`collection-item ${selectedCollections.includes(collection) ? 'selected' : ''}`}
304
304
+
onClick={() => toggleCollection(collection)}
305
305
+
>
298
306
<input
299
307
type="checkbox"
300
300
-
id={`collection-${collection}`}
301
301
-
value={collection}
308
308
+
className="collection-item-checkbox"
302
309
checked={selectedCollections.includes(collection)}
303
303
-
onChange={handleFilterChange}
310
310
+
onChange={() => {}} // Handled by the div onClick
311
311
+
onClick={(e) => e.stopPropagation()}
304
312
/>
305
305
-
<label htmlFor={`collection-${collection}`}>
306
306
-
{collection.split('.').pop()}
307
307
-
</label>
313
313
+
<span className="collection-item-name">{collection}</span>
308
314
</div>
309
315
))}
310
316
</div>
···
7
7
}
8
8
9
9
.feed-item {
10
10
-
background-color: var(--navbar-bg);
11
11
-
border: 1px solid var(--card-border);
12
12
-
border-radius: 8px;
10
10
+
background: var(--navbar-bg);
13
11
padding: 15px;
12
12
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
13
13
+
border: 5px solid var(--card-border);
14
14
+
border-radius: 12px;
14
15
transition: transform 0.2s, box-shadow 0.2s;
15
16
}
16
17
17
18
.feed-item:hover {
18
19
transform: translateY(-2px);
19
19
-
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
20
20
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
20
21
}
21
22
22
23
.feed-item-header {
···
24
25
justify-content: space-between;
25
26
align-items: center;
26
27
margin-bottom: 10px;
28
28
+
border-bottom: 1px solid var(--card-border);
29
29
+
padding-bottom: 10px;
27
30
}
28
31
29
32
.collection-type {
···
41
44
margin-left: 8px;
42
45
font-size: 0.8rem;
43
46
color: var(--text);
44
44
-
opacity: 0.6;
47
47
+
opacity: 0.7;
45
48
}
46
49
47
50
.record-rkey {
···
55
58
}
56
59
57
60
.feed-item-content {
58
58
-
padding: 5px 0;
61
61
+
padding: 10px 0;
59
62
margin-bottom: 10px;
60
60
-
border-top: 1px solid var(--card-border);
61
63
border-bottom: 1px solid var(--card-border);
62
64
}
63
65
···
88
90
color: var(--text);
89
91
opacity: 0.7;
90
92
font-style: italic;
93
93
+
}
94
94
+
95
95
+
/* Dark mode overrides */
96
96
+
.dark-mode .feed-item {
97
97
+
background: #2c2c2c;
98
98
+
border: 5px solid #444;
99
99
+
box-shadow: 0 2px 4px rgba(0,0,0,0.6);
100
100
+
}
101
101
+
102
102
+
.dark-mode .feed-item-header,
103
103
+
.dark-mode .feed-item-content {
104
104
+
border-color: #555;
91
105
}
92
106
93
107
/* Responsive styles */
···
17
17
}
18
18
};
19
19
20
20
-
// Helper to get a readable collection name
21
21
-
const getCollectionDisplayName = (collection) => {
22
22
-
// Extract the last part of the collection name (e.g., 'like' from 'app.bsky.feed.like')
23
23
-
const parts = collection.split('.');
24
24
-
return parts[parts.length - 1];
25
25
-
};
26
26
-
27
20
return (
28
21
<div className="feed-timeline">
29
22
{records.map((record, index) => (
30
23
<div key={`${record.collection}-${record.rkey}-${index}`} className="feed-item">
31
24
<div className="feed-item-header">
32
25
<div className="collection-type">
33
33
-
<span className="collection-name">{getCollectionDisplayName(record.collection)}</span>
26
26
+
<span className="collection-name">{record.collection.split('.').pop()}</span>
34
27
<span className="collection-full">{record.collection}</span>
35
28
</div>
36
29
<div className="record-rkey">{record.rkey}</div>
···
2
2
--navbar-bg: #ffffff;
3
3
--navbar-text: #000000;
4
4
--button-bg: #3B9AF8;
5
5
+
--button-bg-rgb: 59, 154, 248; /* RGB version for transparency */
5
6
--button-text: #ffffff;
6
7
--background: #f0f0f0;
7
8
--text: #000000;
···
14
15
--navbar-bg: #1f1f1f;
15
16
--navbar-text: #f5f5f5;
16
17
--button-bg: #3b9af8;
18
18
+
--button-bg-rgb: 59, 154, 248; /* RGB version for transparency */
17
19
--button-text: #f5f5f5;
18
20
--background: #181818;
19
21
--text: #ffffff;
···
25
27
--navbar-bg: #ffffff;
26
28
--navbar-text: #000000;
27
29
--button-bg: #3B9AF8;
30
30
+
--button-bg-rgb: 59, 154, 248; /* RGB version for transparency */
28
31
--button-text: #ffffff;
29
32
--background: #f0f0f0;
30
33
--text: #000000;