+3
-3
config.ts
+3
-3
config.ts
···
15
15
static readonly FRONTEND_URL: string = "https://deer.social";
16
16
17
17
/**
18
-
* Maximum number of posts to fetch from the PDS per user
19
-
* @default 10
18
+
* Maximum number of posts to show in the feed (across all users)
19
+
* @default 100
20
20
*/
21
-
static readonly MAX_POSTS_PER_USER: number = 22;
21
+
static readonly MAX_POSTS: number = 100;
22
22
23
23
/**
24
24
* Footer text for the dashboard
+5
deno.lock
+5
deno.lock
···
6
6
"npm:@atcute/identity-resolver@~0.1.2": "0.1.2_@atcute+identity@0.1.3",
7
7
"npm:@sveltejs/vite-plugin-svelte@^5.0.3": "5.0.3_svelte@5.28.1__acorn@8.14.1_vite@6.3.2__picomatch@4.0.2",
8
8
"npm:@tsconfig/svelte@^5.0.4": "5.0.4",
9
+
"npm:moment@^2.30.1": "2.30.1",
9
10
"npm:svelte-check@^4.1.5": "4.1.6_svelte@5.28.1__acorn@8.14.1_typescript@5.7.3",
10
11
"npm:svelte@^5.23.1": "5.28.1_acorn@8.14.1",
11
12
"npm:typescript@~5.7.2": "5.7.3",
···
337
338
"@jridgewell/sourcemap-codec"
338
339
]
339
340
},
341
+
"moment@2.30.1": {
342
+
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how=="
343
+
},
340
344
"mri@1.2.0": {
341
345
"integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="
342
346
},
···
470
474
"npm:@atcute/identity-resolver@~0.1.2",
471
475
"npm:@sveltejs/vite-plugin-svelte@^5.0.3",
472
476
"npm:@tsconfig/svelte@^5.0.4",
477
+
"npm:moment@^2.30.1",
473
478
"npm:svelte-check@^4.1.5",
474
479
"npm:svelte@^5.23.1",
475
480
"npm:typescript@~5.7.2",
+2
-1
package.json
+2
-1
package.json
+8
-7
src/App.svelte
+8
-7
src/App.svelte
···
41
41
</main>
42
42
43
43
<style>
44
+
44
45
/* desktop style */
45
-
46
+
46
47
#Content {
47
48
display: flex;
48
49
/* split the screen in half, left for accounts, right for posts */
···
51
52
flex-direction: row;
52
53
justify-content: space-between;
53
54
align-items: center;
54
-
background-color: #12082b;
55
-
color: #ffffff;
55
+
background-color: var(--background-color);
56
+
color: var(--text-color);
56
57
}
57
58
#Feed {
58
59
width: 65%;
···
74
75
width: 35%;
75
76
display: flex;
76
77
flex-direction: column;
77
-
border: 1px solid #8054f0;
78
-
background-color: #0d0620;
78
+
border: 1px solid var(--border-color);
79
+
background-color: var(--content-background-color);
79
80
height: 80vh;
80
81
padding: 20px;
81
82
margin-left: 20px;
···
96
97
margin-bottom: 20px;
97
98
}
98
99
99
-
/* mobile style */
100
-
@media screen and (max-width: 600px) {
100
+
/* mobile style */
101
+
@media screen and (max-width: 600px) {
101
102
#Content {
102
103
flex-direction: column;
103
104
width: auto;
+18
-8
src/app.css
+18
-8
src/app.css
···
3
3
src: url(https://witchcraft.systems/ProggyCleanNerdFont-Regular.ttf);
4
4
}
5
5
6
+
:root {
7
+
--link-color: #646cff;
8
+
--link-hover-color: #535bf2;
9
+
--background-color: #12082b;
10
+
--header-background-color: #1f1145;
11
+
--content-background-color: #0d0620;
12
+
--text-color: white;
13
+
--border-color: #8054f0;
14
+
--indicator-inactive-color: #4a4a4a;
15
+
--indicator-active-color: #8054f0;
16
+
}
17
+
6
18
::-webkit-scrollbar {
7
19
width: 0px;
8
20
background: transparent;
···
35
47
36
48
a {
37
49
font-weight: 500;
38
-
color: #646cff;
50
+
color: var(--link-color);
39
51
text-decoration: inherit;
40
52
}
41
53
a:hover {
42
-
color: #535bf2;
54
+
color: var(--link-hover-color);
43
55
text-decoration: underline;
44
56
}
45
57
···
49
61
place-items: center;
50
62
min-width: 320px;
51
63
min-height: 100vh;
52
-
background-color: #12082b;
64
+
background-color: var(--background-color);
53
65
font-family: 'ProggyClean', monospace;
54
66
font-size: 24px;
55
-
color: white;
56
-
border-color: #8054f0;
67
+
color: var(--text-color);
68
+
border-color: var(--border-color);
57
69
}
58
70
59
71
h1 {
···
68
80
margin-left: auto;
69
81
margin-right: auto;
70
82
text-align: center;
71
-
}
72
-
73
-
83
+
}
+3
-3
src/lib/AccountComponent.svelte
+3
-3
src/lib/AccountComponent.svelte
···
24
24
display: flex;
25
25
text-align: start;
26
26
align-items: center;
27
-
background-color: #12082b;
27
+
background-color: var(--background-color);
28
28
padding: 0px;
29
29
margin-bottom: 15px;
30
-
border: 1px solid #8054f0;
30
+
border: 1px solid var(--border-color);
31
31
}
32
32
#accountName {
33
33
margin-left: 10px;
···
43
43
width: 50px;
44
44
height: 50px;
45
45
margin: 0px;
46
-
border-right: #8054f0 1px solid;
46
+
border-right: var(--border-color) 1px solid;
47
47
}
48
48
</style>
+20
-15
src/lib/PostComponent.svelte
+20
-15
src/lib/PostComponent.svelte
···
2
2
import { Post } from "./pdsfetch";
3
3
import { Config } from "../../config";
4
4
import { onMount } from "svelte";
5
+
import moment from "moment";
5
6
6
7
let { post }: { post: Post } = $props();
7
8
···
76
77
<a
77
78
id="postLink"
78
79
href="{Config.FRONTEND_URL}/profile/{post.authorDid}/post/{post.recordName}"
79
-
>{post.timenotstamp}</a
80
+
>{moment(post.timenotstamp).isBefore(moment().subtract(1, "month"))
81
+
? moment(post.timenotstamp).format("MMM D, YYYY")
82
+
: moment(post.timenotstamp).fromNow()}</a
80
83
>
81
84
</p>
82
85
</div>
···
131
134
</div>
132
135
{/if}
133
136
{#if post.videosLinkCid}
137
+
<!-- svelte-ignore a11y_media_has_caption -->
134
138
<video
135
139
id="embedVideo"
136
140
src="{Config.PDS_URL}/xrpc/com.atproto.sync.getBlob?did={post.authorDid}&cid={post.videosLinkCid}"
···
141
145
</div>
142
146
143
147
<style>
148
+
144
149
a:hover {
145
150
text-decoration: underline;
146
151
}
147
152
#postContainer {
148
153
display: flex;
149
154
flex-direction: column;
150
-
border: 1px solid #8054f0;
151
-
background-color: black;
155
+
border: 1px solid var(--border-color);
156
+
background-color: var(--background-color);
152
157
margin-bottom: 15px;
153
158
overflow-wrap: break-word;
154
159
}
···
157
162
flex-direction: row;
158
163
align-items: center;
159
164
justify-content: start;
160
-
background-color: #1f1145;
165
+
background-color: var(--header-background-color);
161
166
padding: 0px 0px;
162
167
height: fit-content;
163
-
border-bottom: 1px solid #8054f0;
168
+
border-bottom: 1px solid var(--border-color);
164
169
font-weight: bold;
165
170
overflow-wrap: break-word;
166
171
height: 60px;
167
172
}
168
173
#displayName {
169
-
color: white;
174
+
color: var(--text-color);
170
175
font-size: 1.2em;
171
176
padding: 0;
172
177
margin: 0;
173
178
}
174
179
#handle {
175
-
color: #8054f0;
180
+
color: var(--border-color);
176
181
font-size: 0.8em;
177
182
padding: 0;
178
183
margin: 0;
179
184
}
180
185
181
186
#postLink {
182
-
color: #8054f0;
187
+
color: var(--border-color);
183
188
font-size: 0.8em;
184
189
padding: 0;
185
190
margin: 0;
···
189
194
text-align: start;
190
195
flex-direction: column;
191
196
padding: 10px;
192
-
background-color: #0d0620;
193
-
color: white;
197
+
background-color: var(--content-background-color);
198
+
color: var(--text-color);
194
199
overflow-wrap: break-word;
195
200
}
196
201
#replyingText {
···
220
225
height: 100%;
221
226
margin: 0px;
222
227
margin-left: 0px;
223
-
border-right: #8054f0 1px solid;
228
+
border-right: var(--border-color) 1px solid;
224
229
}
225
230
#carouselContainer {
226
231
position: relative;
···
245
250
.indicator {
246
251
width: 8px;
247
252
height: 8px;
248
-
background-color: #4a4a4a;
253
+
background-color: var(--indicator-inactive-color);
249
254
}
250
255
.indicator.active {
251
-
background-color: #8054f0;
256
+
background-color: var(--indicator-active-color);
252
257
}
253
258
#prevBtn,
254
259
#nextBtn {
255
260
background-color: rgba(31, 17, 69, 0.7);
256
-
color: white;
257
-
border: 1px solid #8054f0;
261
+
color: var(--text-color);
262
+
border: 1px solid var(--border-color);
258
263
width: 30px;
259
264
height: 30px;
260
265
cursor: pointer;
+8
-14
src/lib/pdsfetch.ts
+8
-14
src/lib/pdsfetch.ts
···
48
48
account: AccountMetadata,
49
49
) {
50
50
this.postCid = record.cid;
51
-
this.recordName = record.uri.split("/").slice(-1)[0];
51
+
this.recordName = processAtUri(record.uri).rkey;
52
52
this.authorDid = account.did;
53
53
this.authorAvatarCid = account.avatarCid;
54
54
this.authorHandle = account.handle;
···
111
111
}),
112
112
});
113
113
114
-
const getDidsFromPDS = async () => {
114
+
const getDidsFromPDS = async () : Promise<At.Did[]> => {
115
115
const { data } = await rpc.get("com.atproto.sync.listRepos", {
116
116
params: {},
117
117
});
118
-
return data.repos.map((repo: any) => (repo.did));
118
+
return data.repos.map((repo: any) => (repo.did)) as At.Did[];
119
119
};
120
-
const getAccountMetadata = async (did: `did:${string}:${string}`) => {
120
+
const getAccountMetadata = async (did: `did:${string}:${string}`) : Promise<AccountMetadata> => {
121
121
// gonna assume self exists in the app.bsky.actor.profile
122
122
try {
123
123
const { data } = await rpc.get("com.atproto.repo.getRecord", {
···
146
146
did: "error",
147
147
displayName: "",
148
148
avatarCid: null,
149
+
handle: "error",
149
150
};
150
151
}
151
152
};
152
153
153
-
const getAllMetadataFromPds = async () => {
154
+
const getAllMetadataFromPds = async () : Promise<AccountMetadata[]> => {
154
155
const dids = await getDidsFromPDS();
155
156
const metadata = await Promise.all(
156
157
dids.map(async (repo: `did:${string}:${string}`) => {
···
166
167
params: {
167
168
repo: did as At.Identifier,
168
169
collection: "app.bsky.feed.post",
169
-
limit: Config.MAX_POSTS_PER_USER,
170
+
limit: Config.MAX_POSTS,
170
171
},
171
172
});
172
173
return {
···
237
238
})
238
239
);
239
240
posts.sort((a, b) => b.timestamp - a.timestamp);
240
-
return posts;
241
-
};
242
-
243
-
const testApiCall = async () => {
244
-
const { data } = await rpc.get("com.atproto.sync.listRepos", {
245
-
params: {},
246
-
});
247
-
console.log(data);
241
+
return posts.slice(0, Config.MAX_POSTS);
248
242
};
249
243
export { fetchAllPosts, getAllMetadataFromPds, Post };
250
244
export type { AccountMetadata };