tangled
alpha
login
or
join now
stevedylan.dev
/
docs.surf
A fullstack app for indexing standard.site documents
2
fork
atom
overview
issues
pulls
pipelines
chore: updated styles
stevedylan.dev
3 weeks ago
6100dac6
8fd4398d
+144
-113
1 changed file
expand all
collapse all
unified
split
packages
client
src
App.tsx
+144
-113
packages/client/src/App.tsx
···
75
75
76
76
const formatDate = (dateString?: string) => {
77
77
if (!dateString) return "Unknown date";
78
78
-
return new Date(dateString).toLocaleDateString("en-US", {
78
78
+
const date = new Date(dateString);
79
79
+
const now = new Date();
80
80
+
const diff = now.getTime() - date.getTime();
81
81
+
const minutes = Math.floor(diff / 60000);
82
82
+
const hours = Math.floor(diff / 3600000);
83
83
+
const days = Math.floor(diff / 86400000);
84
84
+
85
85
+
if (minutes < 1) return "just now";
86
86
+
if (minutes < 60) return `${minutes} minute${minutes > 1 ? "s" : ""} ago`;
87
87
+
if (hours < 24) return `${hours} hour${hours > 1 ? "s" : ""} ago`;
88
88
+
if (days < 7) return `${days} day${days > 1 ? "s" : ""} ago`;
89
89
+
90
90
+
return date.toLocaleDateString("en-US", {
79
91
year: "numeric",
80
92
month: "long",
81
93
day: "numeric",
···
92
104
return doc.description || doc.textContent || "";
93
105
};
94
106
95
95
-
96
107
return (
97
108
<div className="window" style={{ width: "100%", maxWidth: "800px" }}>
98
109
<div className="title-bar">
99
99
-
<div className="title-bar-text">docs.surf - Internet Explorer 6</div>
110
110
+
<div className="title-bar-text">Internet Explorer 6</div>
100
111
<div className="title-bar-controls">
101
112
<button aria-label="Minimize" />
102
113
<button aria-label="Maximize" />
···
137
148
)}
138
149
139
150
{!loading && !error && (
140
140
-
<div className="feed" style={{ maxHeight: "70vh", overflowY: "auto", paddingRight: "5px" }}>
141
141
-
{documents.map((doc) => (
142
142
-
<fieldset key={doc.uri} style={{ marginBottom: "16px" }}>
143
143
-
<legend style={{ fontWeight: "bold" }}>
144
144
-
{doc.viewUrl ? (
145
145
-
<a
146
146
-
href={doc.viewUrl}
147
147
-
target="_blank"
148
148
-
rel="noopener noreferrer"
149
149
-
style={{ color: "inherit", textDecoration: "none" }}
150
150
-
>
151
151
-
{doc.title}
152
152
-
</a>
151
151
+
<div
152
152
+
className="feed"
153
153
+
style={{
154
154
+
maxHeight: "70vh",
155
155
+
overflowY: "auto",
156
156
+
paddingRight: "5px",
157
157
+
}}
158
158
+
>
159
159
+
{documents.map((doc, index) => (
160
160
+
<div
161
161
+
key={doc.uri}
162
162
+
style={{
163
163
+
display: "flex",
164
164
+
gap: "12px",
165
165
+
padding: "16px",
166
166
+
borderBottom:
167
167
+
index < documents.length - 1 ? "1px solid #e0e0e0" : "none",
168
168
+
backgroundColor: "#ffffff",
169
169
+
position: "relative",
170
170
+
}}
171
171
+
>
172
172
+
{/* Thumbnail on the left */}
173
173
+
<div style={{ flexShrink: 0 }}>
174
174
+
{doc.coverImageUrl || doc.publication?.iconUrl ? (
175
175
+
<img
176
176
+
src={doc.coverImageUrl || doc.publication?.iconUrl}
177
177
+
alt={doc.title}
178
178
+
style={{
179
179
+
width: "88px",
180
180
+
height: "88px",
181
181
+
objectFit: "scale-down",
182
182
+
border: "1px solid #d0d0d0",
183
183
+
}}
184
184
+
/>
153
185
) : (
154
154
-
doc.title
155
155
-
)}
156
156
-
</legend>
157
157
-
<div style={{ padding: "8px" }}>
158
158
-
{/* Publication info */}
159
159
-
{doc.publication && (
160
186
<div
161
187
style={{
188
188
+
width: "88px",
189
189
+
height: "88px",
190
190
+
backgroundColor: "#f0f0f0",
191
191
+
border: "1px solid #d0d0d0",
162
192
display: "flex",
163
193
alignItems: "center",
164
164
-
gap: "8px",
165
165
-
marginBottom: "8px",
166
166
-
fontSize: "0.85em",
194
194
+
justifyContent: "center",
195
195
+
fontSize: "10px",
196
196
+
color: "#999",
167
197
}}
168
198
>
169
169
-
{doc.publication.iconUrl && (
170
170
-
<img
171
171
-
src={doc.publication.iconUrl}
172
172
-
alt={doc.publication.name}
173
173
-
style={{
174
174
-
width: "16px",
175
175
-
height: "16px",
176
176
-
objectFit: "cover",
177
177
-
}}
178
178
-
/>
179
179
-
)}
180
180
-
<span style={{ fontWeight: "bold" }}>
181
181
-
{doc.publication.name}
182
182
-
</span>
183
183
-
</div>
184
184
-
)}
185
185
-
186
186
-
{/* Cover image */}
187
187
-
{doc.coverImageUrl && (
188
188
-
<div style={{ marginBottom: "8px" }}>
189
189
-
<img
190
190
-
src={doc.coverImageUrl}
191
191
-
alt={doc.title}
192
192
-
style={{
193
193
-
maxWidth: "100%",
194
194
-
maxHeight: "200px",
195
195
-
objectFit: "cover",
196
196
-
border: "1px solid #888",
197
197
-
}}
198
198
-
/>
199
199
+
No Image
199
200
</div>
200
201
)}
202
202
+
</div>
201
203
202
202
-
{/* Date */}
203
203
-
<div
204
204
+
{/* Content on the right */}
205
205
+
<div style={{ flex: 1, minWidth: 0 }}>
206
206
+
{/* Title */}
207
207
+
<h3
204
208
style={{
205
205
-
marginBottom: "8px",
206
206
-
fontSize: "0.85em",
207
207
-
color: "#666",
209
209
+
margin: "0 0 8px 0",
210
210
+
fontSize: "15px",
211
211
+
fontWeight: "normal",
212
212
+
color: "#333",
213
213
+
lineHeight: "1.3",
208
214
}}
209
215
>
210
210
-
Published: {formatDate(doc.publishedAt)}
211
211
-
{doc.updatedAt && doc.updatedAt !== doc.publishedAt && (
212
212
-
<> | Updated: {formatDate(doc.updatedAt)}</>
216
216
+
{doc.viewUrl ? (
217
217
+
<a
218
218
+
href={doc.viewUrl}
219
219
+
target="_blank"
220
220
+
rel="noopener noreferrer"
221
221
+
style={{
222
222
+
color: "#333",
223
223
+
textDecoration: "none",
224
224
+
}}
225
225
+
>
226
226
+
{doc.title}
227
227
+
</a>
228
228
+
) : (
229
229
+
doc.title
213
230
)}
214
214
-
</div>
231
231
+
</h3>
215
232
216
233
{/* Description */}
217
234
{getDescription(doc) && (
218
218
-
<p style={{ marginBottom: "12px" }}>
219
219
-
{truncateText(getDescription(doc))}
235
235
+
<p
236
236
+
style={{
237
237
+
margin: "0 0 8px 0",
238
238
+
fontSize: "12px",
239
239
+
color: "#666",
240
240
+
lineHeight: "1.4",
241
241
+
}}
242
242
+
>
243
243
+
{truncateText(getDescription(doc), 150)}
220
244
</p>
221
245
)}
222
246
223
223
-
{/* Tags */}
224
224
-
{doc.tags && doc.tags.length > 0 && (
225
225
-
<div
247
247
+
{/* Publication name and timestamp */}
248
248
+
<div
249
249
+
style={{
250
250
+
display: "flex",
251
251
+
alignItems: "center",
252
252
+
justifyContent: "space-between",
253
253
+
fontSize: "12px",
254
254
+
}}
255
255
+
>
256
256
+
<a
257
257
+
href={doc.publication?.url}
258
258
+
target="_blank"
259
259
+
rel="noreferrer"
226
260
style={{
227
227
-
display: "flex",
228
228
-
flexWrap: "wrap",
229
229
-
gap: "4px",
230
230
-
marginBottom: "12px",
261
261
+
color: "#7aaa3c",
262
262
+
fontWeight: "bold",
231
263
}}
232
264
>
233
233
-
{doc.tags.map((tag) => (
234
234
-
<span
235
235
-
key={tag}
236
236
-
style={{
237
237
-
background: "#c0c0c0",
238
238
-
padding: "2px 6px",
239
239
-
fontSize: "0.75em",
240
240
-
border: "1px solid #808080",
241
241
-
}}
242
242
-
>
243
243
-
{tag}
244
244
-
</span>
245
245
-
))}
246
246
-
</div>
247
247
-
)}
248
248
-
249
249
-
{/* Actions */}
250
250
-
<div style={{ display: "flex", gap: "8px", justifyContent: "flex-end" }}>
251
251
-
{doc.bskyPostRef && (
252
252
-
<button
253
253
-
onClick={() =>
254
254
-
window.open(
255
255
-
`https://bsky.app/profile/${doc.did}/post/${doc.bskyPostRef!.uri.split("/").pop()}`,
256
256
-
"_blank"
257
257
-
)
258
258
-
}
259
259
-
>
260
260
-
View on Bluesky
261
261
-
</button>
262
262
-
)}
263
263
-
{doc.viewUrl && (
264
264
-
<button
265
265
-
onClick={() =>
266
266
-
window.open(doc.viewUrl || "", "_blank")
267
267
-
}
268
268
-
>
269
269
-
Read More
270
270
-
</button>
271
271
-
)}
265
265
+
{doc.publication?.name || "Unknown"}
266
266
+
</a>
267
267
+
<a
268
268
+
href={`https://pdsls.dev/${doc.uri}`}
269
269
+
target="_blank"
270
270
+
rel="noreferrer"
271
271
+
style={{
272
272
+
color: "#999",
273
273
+
}}
274
274
+
>
275
275
+
{formatDate(doc.publishedAt)}
276
276
+
</a>
272
277
</div>
273
278
</div>
274
274
-
</fieldset>
279
279
+
280
280
+
{/* RSS icon on the far right */}
281
281
+
{/*<div style={{ flexShrink: 0 }}>
282
282
+
<svg
283
283
+
width="24"
284
284
+
height="24"
285
285
+
viewBox="0 0 24 24"
286
286
+
fill="none"
287
287
+
xmlns="http://www.w3.org/2000/svg"
288
288
+
style={{ opacity: 0.6 }}
289
289
+
>
290
290
+
<circle cx="6" cy="18" r="2" fill="#ff6600" />
291
291
+
<path
292
292
+
d="M4 4c9.941 0 18 8.059 18 18"
293
293
+
stroke="#ff6600"
294
294
+
strokeWidth="2"
295
295
+
fill="none"
296
296
+
/>
297
297
+
<path
298
298
+
d="M4 11c6.075 0 11 4.925 11 11"
299
299
+
stroke="#ff6600"
300
300
+
strokeWidth="2"
301
301
+
fill="none"
302
302
+
/>
303
303
+
</svg>
304
304
+
</div>*/}
305
305
+
</div>
275
306
))}
276
307
{documents.length === 0 && <p>No documents found.</p>}
277
308
</div>