diff --git a/src/app/curator/homework/[submissionId]/page.tsx b/src/app/curator/homework/[submissionId]/page.tsx
index a70b38a..9bf6031 100644
--- a/src/app/curator/homework/[submissionId]/page.tsx
+++ b/src/app/curator/homework/[submissionId]/page.tsx
@@ -229,7 +229,7 @@ export default async function SubmissionPage({ params }: Props) {
{new Date(fb.createdAt).toLocaleString("ru-RU")}
-
{fb.text}
+ {linkify(fb.text)}
{fbFiles.length > 0 && (
{fbFiles.map((f) => (
diff --git a/src/lib/__tests__/comment-reply-email.test.ts b/src/lib/__tests__/comment-reply-email.test.ts
index bf12309..11d3440 100644
--- a/src/lib/__tests__/comment-reply-email.test.ts
+++ b/src/lib/__tests__/comment-reply-email.test.ts
@@ -83,4 +83,20 @@ describe("sendCommentReplyEmail", () => {
expect(html).not.toContain("Отвечает
");
expect(html).toContain("Привет!");
});
+
+ it("делает ссылки в тексте ответа кликабельными", async () => {
+ await sendCommentReplyEmail(
+ "student@example.com",
+ "Екатерина",
+ "6.3. Холст",
+ "Подарок тут: https://obsidian.second-brain.ru/secret-bonus?a=1&b=2. Пишите!",
+ LESSON_URL
+ );
+
+ const html = sent[0].html;
+ expect(html).toContain(
+ '. Пишите!");
+ });
});
diff --git a/src/lib/email.ts b/src/lib/email.ts
index 7210132..386e42a 100644
--- a/src/lib/email.ts
+++ b/src/lib/email.ts
@@ -1,5 +1,6 @@
import { Resend } from "resend";
import { getSetting } from "./settings";
+import { URL_RE } from "./linkify";
function getResend() {
return new Resend(process.env.RESEND_API_KEY);
@@ -97,11 +98,24 @@ function escapeHtml(text: string) {
.replace(/"/g, """);
}
+// Ссылки в цитате — кликабельные: не все почтовики сами превращают голый адрес в ссылку.
+// Экранируем по частям, чтобы `&` в адресе не превратился в `&` внутри href дважды.
+function linkifyHtml(text: string) {
+ return text
+ .split(URL_RE)
+ .map((part, i) =>
+ i % 2 === 1
+ ? `${escapeHtml(part)}`
+ : escapeHtml(part)
+ )
+ .join("");
+}
+
function quote(text: string) {
return `
|
- ${escapeHtml(text).replace(/\n/g, " ")}
+ ${linkifyHtml(text).replace(/\n/g, " ")}
|
`;
diff --git a/src/lib/linkify.tsx b/src/lib/linkify.tsx
index 1a56668..1f6a601 100644
--- a/src/lib/linkify.tsx
+++ b/src/lib/linkify.tsx
@@ -1,6 +1,6 @@
import { Fragment, type ReactNode } from "react";
-const URL_RE = /(https?:\/\/[^\s<>()]+[^\s<>().,;:!?])/g;
+export const URL_RE = /(https?:\/\/[^\s<>()]+[^\s<>().,;:!?])/g;
export function linkify(text: string): ReactNode[] {
if (!text) return [];