tangled
alpha
login
or
join now
leaflet.pub
/
leaflet
289
fork
atom
a tool for shared writing and social publishing
289
fork
atom
overview
issues
28
pulls
pipelines
parse and render description
awarm.space
3 months ago
a6f5e830
6b62f1f2
+58
-4
1 changed file
expand all
collapse all
unified
split
app
(home-pages)
p
[didOrHandle]
ProfileHeader.tsx
+58
-4
app/(home-pages)/p/[didOrHandle]/ProfileHeader.tsx
···
9
9
import { Json } from "supabase/database.types";
10
10
import { BlueskyTiny } from "components/Icons/BlueskyTiny";
11
11
import { ProfileViewDetailed } from "@atproto/api/dist/client/types/app/bsky/actor/defs";
12
12
+
import { SpeedyLink } from "components/SpeedyLink";
13
13
+
import { ReactNode } from "react";
12
14
13
15
export const ProfileHeader = (props: {
14
16
profile: ProfileViewDetailed;
15
17
publications: { record: Json; uri: string }[];
16
18
}) => {
17
17
-
console.log(props.profile);
18
19
let profileRecord = props.profile;
19
20
20
21
return (
···
37
38
@{props.profile.handle}
38
39
</div>
39
40
)}
40
40
-
<div className="text-secondary px-3 sm:px-4 ">
41
41
-
{profileRecord.description}
42
42
-
</div>
41
41
+
<pre className="text-secondary px-3 sm:px-4 ">
42
42
+
{profileRecord.description
43
43
+
? parseDescription(profileRecord.description)
44
44
+
: null}
45
45
+
</pre>
43
46
<div className=" w-full overflow-x-scroll mt-3 mb-6 ">
44
47
<div className="grid grid-flow-col auto-cols-[164px] sm:auto-cols-[240px] gap-2 mx-auto w-fit px-3 sm:px-4 ">
45
48
{/*<div className="spacer "/>*/}
···
103
106
</a>
104
107
);
105
108
};
109
109
+
110
110
+
function parseDescription(description: string): ReactNode[] {
111
111
+
const combinedRegex = /(@\S+|https?:\/\/\S+)/g;
112
112
+
113
113
+
const parts: ReactNode[] = [];
114
114
+
let lastIndex = 0;
115
115
+
let match;
116
116
+
let key = 0;
117
117
+
118
118
+
while ((match = combinedRegex.exec(description)) !== null) {
119
119
+
// Add text before this match
120
120
+
if (match.index > lastIndex) {
121
121
+
parts.push(description.slice(lastIndex, match.index));
122
122
+
}
123
123
+
124
124
+
const matched = match[0];
125
125
+
126
126
+
if (matched.startsWith("@")) {
127
127
+
// It's a mention
128
128
+
const handle = matched.slice(1);
129
129
+
parts.push(
130
130
+
<SpeedyLink key={key++} href={`/p/${handle}`}>
131
131
+
{matched}
132
132
+
</SpeedyLink>,
133
133
+
);
134
134
+
} else {
135
135
+
// It's a URL
136
136
+
const urlWithoutProtocol = matched
137
137
+
.replace(/^https?:\/\//, "")
138
138
+
.replace(/\/+$/, "");
139
139
+
const displayText =
140
140
+
urlWithoutProtocol.length > 50
141
141
+
? urlWithoutProtocol.slice(0, 50) + "…"
142
142
+
: urlWithoutProtocol;
143
143
+
parts.push(
144
144
+
<a key={key++} href={matched} target="_blank" rel="noopener noreferrer">
145
145
+
{displayText}
146
146
+
</a>,
147
147
+
);
148
148
+
}
149
149
+
150
150
+
lastIndex = match.index + matched.length;
151
151
+
}
152
152
+
153
153
+
// Add remaining text after last match
154
154
+
if (lastIndex < description.length) {
155
155
+
parts.push(description.slice(lastIndex));
156
156
+
}
157
157
+
158
158
+
return parts;
159
159
+
}