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