better url parsing (#349)

This commit is contained in:
BlowaterNostr 2023-12-07 21:18:26 +08:00 committed by GitHub
parent f72621a167
commit a9afd8291d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 83 additions and 8 deletions

View File

@ -1,5 +1,5 @@
import { assertEquals } from "https://deno.land/std@0.176.0/testing/asserts.ts";
import { ChatMessage, groupContinuousMessages, parseContent } from "./message.ts";
import { ChatMessage, findUrlInString, groupContinuousMessages, parseContent } from "./message.ts";
import { PrivateKey, PublicKey } from "../lib/nostr-ts/key.ts";
import { Nevent, NostrAddress } from "../lib/nostr-ts/nip19.ts";
import { NostrKind } from "../lib/nostr-ts/nostr.ts";
@ -287,3 +287,24 @@ Deno.test("if there is no message, should not yield any group", () => {
assertEquals(group.value, undefined);
assertEquals(group.done, true);
});
Deno.test("findUrlInString should include non-URL parts", () => {
const result = findUrlInString("Visit http://example.com for more info.");
assertEquals(result, ["Visit ", new URL("http://example.com"), " for more info."]);
});
Deno.test("findUrlInString with multiple URLs and text parts", () => {
const result = findUrlInString("Go to http://example.com and https://example.org for info.");
assertEquals(result, [
"Go to ",
new URL("http://example.com"),
" and ",
new URL("https://example.org"),
" for info.",
]);
});
Deno.test("findUrlInString with only text", () => {
const result = findUrlInString("No URLs here.");
assertEquals(result, ["No URLs here."]);
});

View File

@ -227,3 +227,32 @@ export function sortMessage(messages: ChatMessage[]) {
return m2.created_at.getTime() - m1.created_at.getTime();
});
}
// credit to GPT4
export function findUrlInString(text: string): (string | URL)[] {
// Regular expression for URLs with various protocols
const urlRegex = /[a-zA-Z][a-zA-Z0-9+.-]*:\/\/[^\s]+/g;
// Split the text into URL and non-URL parts
let parts = text.split(urlRegex);
// Find all URLs using the regex
const foundUrls = text.match(urlRegex) || [];
// Interleave non-URL parts and URL parts
let result: (string | URL)[] = [];
parts.forEach((part, index) => {
if (part !== "") {
result.push(part);
}
if (index < foundUrls.length) {
try {
result.push(new URL(foundUrls[index]));
} catch {
result.push(foundUrls[index]);
}
}
});
return result;
}

View File

@ -11,6 +11,7 @@ import { KeyIcon } from "./icons/key-icon.tsx";
import { UserIcon } from "./icons/user-icon.tsx";
import { CopyButton } from "./components/copy-button.tsx";
import { LinkColor } from "./style/colors.ts";
import { findUrlInString } from "./message.ts";
type UserDetailProps = {
targetUserProfile: ProfileData;
@ -20,7 +21,7 @@ type UserDetailProps = {
export function UserDetail(props: UserDetailProps) {
return (
<div class={tw`p-2 relative`}>
<div class={tw`p-2 relative text-[#7A818C]`}>
<Avatar
class={tw`w-64 h-64 m-auto mt-8`}
picture={props.targetUserProfile.picture}
@ -68,9 +69,9 @@ export function UserDetail(props: UserDetailProps) {
}}
/>
<p
class={tw`flex-1 text-[#7A818C] group-hover:text-[#F3F4EA] break-words overflow-hidden`}
class={tw`flex-1 break-words overflow-hidden`}
>
{props.targetUserProfile.about}
{TextWithLinks({ text: props.targetUserProfile.about })}
</p>
</div>
)
@ -87,11 +88,9 @@ export function UserDetail(props: UserDetailProps) {
}}
/>
<p
class={tw`flex-1 text-[${LinkColor}] group-hover:text-[#F3F4EA] break-words overflow-hidden`}
class={tw`flex-1 break-words overflow-hidden`}
>
<a href={props.targetUserProfile.website} target="_blank">
{props.targetUserProfile.website}
</a>
{TextWithLinks({ text: props.targetUserProfile.website })}
</p>
</div>
)
@ -99,3 +98,29 @@ export function UserDetail(props: UserDetailProps) {
</div>
);
}
function TextWithLinks({ text }: { text: string }) {
const parts = findUrlInString(text);
return (
<div>
{parts.map((part, index) => {
if (part instanceof URL) {
return (
<a
class={tw`text-[${LinkColor}] hover:text-[#F3F4EA]`}
key={index}
href={part.href}
target="_blank"
rel="noopener noreferrer"
>
{part.href}
</a>
);
} else {
return <span key={index}>{part}</span>;
}
})}
</div>
);
}