Skip to content

Commit 119f194

Browse files
authored
Merge pull request #23 from techdiary-dev/editor
Refactor article handle generation logic in article.actions.ts to sup…
2 parents 22f125a + 8ae4d06 commit 119f194

File tree

4 files changed

+116
-30
lines changed

4 files changed

+116
-30
lines changed

src/app/api/play/route.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
import { slugify } from "@/lib/slug-helper.util";
1+
import * as articleActions from "@/backend/services/article.actions";
22
import { NextResponse } from "next/server";
33

44
export async function GET(request: Request) {
5-
// const _headers = await headers();
65
return NextResponse.json({
7-
slug: slugify("কেমন আছেন আপনারা?"),
6+
handle: await articleActions.getUniqueArticleHandle(
7+
"untitled",
8+
"fc6cfc91-f017-4923-9706-8813ae8df621"
9+
),
810
});
911
}

src/backend/services/article.actions.ts

Lines changed: 80 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import {
1010
desc,
1111
eq,
1212
joinTable,
13+
like,
1314
neq,
15+
or,
1416
} from "../persistence/persistence-where-operator";
1517
import { PersistentRepository } from "../persistence/persistence.repository";
1618
import {
@@ -84,18 +86,86 @@ export async function createMyArticle(
8486
}
8587
}
8688

87-
export const getUniqueArticleHandle = async (title: string) => {
89+
export const getUniqueArticleHandle = async (
90+
title: string,
91+
ignoreArticleId?: string
92+
) => {
8893
try {
89-
const count = await articleRepository.findRowCount({
90-
where: eq("handle", slugify(title)),
91-
columns: ["id", "handle"],
94+
// Slugify the title first
95+
const baseHandle = slugify(title);
96+
97+
// If we have an ignoreArticleId, check if this article already exists
98+
if (ignoreArticleId) {
99+
const [existingArticle] = await articleRepository.findRows({
100+
where: eq("id", ignoreArticleId),
101+
columns: ["id", "handle"],
102+
limit: 1,
103+
});
104+
105+
// If the article exists and its handle is already the slugified title,
106+
// we can just return that handle (no need to append a number)
107+
if (existingArticle && existingArticle.handle === baseHandle) {
108+
return baseHandle;
109+
}
110+
}
111+
112+
// Find all articles with the same base handle or handles that have numeric suffixes
113+
const handlePattern = `${baseHandle}-%`;
114+
let baseHandleWhereClause: any = eq<Article, keyof Article>(
115+
"handle",
116+
baseHandle
117+
);
118+
let suffixWhereClause: any = like<Article>("handle", handlePattern);
119+
120+
let whereClause: any = or(baseHandleWhereClause, suffixWhereClause);
121+
122+
if (ignoreArticleId) {
123+
whereClause = and(
124+
whereClause,
125+
neq<Article, keyof Article>("id", ignoreArticleId)
126+
);
127+
}
128+
129+
// Get all existing handles that match our patterns
130+
const existingArticles = await articleRepository.findRows({
131+
where: whereClause,
132+
columns: ["handle"],
92133
});
93-
if (count) {
94-
return `${slugify(title)}-${count + 1}`;
134+
135+
// If no existing handles found, return the base handle
136+
if (existingArticles.length === 0) {
137+
return baseHandle;
95138
}
96-
return slugify(title);
139+
140+
// Check if the exact base handle exists
141+
const exactBaseExists = existingArticles.some(
142+
(article) => article.handle === baseHandle
143+
);
144+
145+
// If the exact base handle doesn't exist, we can use it
146+
if (!exactBaseExists) {
147+
return baseHandle;
148+
}
149+
150+
// Find the highest numbered suffix
151+
let highestNumber = 1;
152+
const regex = new RegExp(`^${baseHandle}-(\\d+)$`);
153+
154+
existingArticles.forEach((article) => {
155+
const match = article.handle.match(regex);
156+
if (match) {
157+
const num = parseInt(match[1], 10);
158+
if (num >= highestNumber) {
159+
highestNumber = num + 1;
160+
}
161+
}
162+
});
163+
164+
// Return with the next number in sequence
165+
return `${baseHandle}-${highestNumber}`;
97166
} catch (error) {
98167
handleRepositoryException(error);
168+
throw error;
99169
}
100170
};
101171

@@ -116,7 +186,9 @@ export async function updateArticle(
116186
where: eq("id", input.article_id),
117187
data: {
118188
title: input.title,
119-
handle: input.handle,
189+
handle: input.handle
190+
? await getUniqueArticleHandle(input.handle, input.article_id)
191+
: undefined,
120192
excerpt: input.excerpt,
121193
body: input.body,
122194
cover_image: input.cover_image,

src/components/Editor/ArticleEditor.tsx

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,14 @@ const ArticleEditor: React.FC<Prop> = ({ article, uuid }) => {
4444
const appConfig = useAppConfirm();
4545
const titleRef = useRef<HTMLTextAreaElement>(null!);
4646
const bodyRef = useRef<HTMLTextAreaElement | null>(null);
47-
const setDebouncedTitle = useDebouncedCallback(() => handleSaveTitle(), 1000);
48-
const setDebouncedBody = useDebouncedCallback(() => handleSaveBody(), 1000);
47+
const setDebouncedTitle = useDebouncedCallback(
48+
(title: string) => handleDebouncedSaveTitle(title),
49+
1000
50+
);
51+
const setDebouncedBody = useDebouncedCallback(
52+
(body: string) => handleDebouncedSaveBody(body),
53+
1000
54+
);
4955

5056
const [editorMode, selectEditorMode] = React.useState<"write" | "preview">(
5157
"write"
@@ -91,39 +97,43 @@ const ArticleEditor: React.FC<Prop> = ({ article, uuid }) => {
9197
},
9298
});
9399

94-
const handleSaveTitle = () => {
100+
const handleSaveArticleOnBlurTitle = (title: string) => {
95101
if (!uuid) {
96-
if (editorForm.watch("title")) {
102+
if (title) {
97103
articleCreateMutation.mutate({
98-
title: editorForm.watch("title") ?? "",
104+
title: title ?? "",
99105
});
100106
}
101107
}
108+
};
102109

110+
const handleDebouncedSaveTitle = (title: string) => {
103111
if (uuid) {
104-
if (editorForm.watch("title")) {
112+
if (title) {
105113
updateMyArticleMutation.mutate({
114+
title: title ?? "",
106115
article_id: uuid,
107-
title: editorForm.watch("title") ?? "",
108116
});
109117
}
110118
}
111119
};
112120

113-
const handleSaveBody = () => {
114-
// if (!uuid) {
115-
// if (editorForm.watch("body")) {
116-
// articleCreateMutation.mutate({
117-
// title: editorForm.watch("body") ?? "",
118-
// });
119-
// }
120-
// }
121-
121+
const handleDebouncedSaveBody = (body: string) => {
122122
if (uuid) {
123-
if (editorForm.watch("body")) {
123+
if (body) {
124124
updateMyArticleMutation.mutate({
125125
article_id: uuid,
126-
body: editorForm.watch("body") ?? "",
126+
handle: article?.handle ?? "untitled",
127+
body,
128+
});
129+
}
130+
} else {
131+
if (body) {
132+
articleCreateMutation.mutate({
133+
title: editorForm.watch("title")?.length
134+
? (editorForm.watch("title") ?? "untitled")
135+
: "untitled",
136+
body,
127137
});
128138
}
129139
}
@@ -229,7 +239,7 @@ const ArticleEditor: React.FC<Prop> = ({ article, uuid }) => {
229239
value={editorForm.watch("title")}
230240
className="w-full text-2xl focus:outline-none bg-background resize-none"
231241
ref={titleRef}
232-
onBlur={() => handleSaveTitle()}
242+
onBlur={(e) => handleSaveArticleOnBlurTitle(e.target.value)}
233243
onChange={(e) => {
234244
editorForm.setValue("title", e.target.value);
235245
setDebouncedTitle(e.target.value);

src/components/Editor/ArticleEditorDrawer.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ const ArticleEditorDrawer: React.FC<Props> = ({ article, open, onClose }) => {
105105
onSubmit={form.handleSubmit(handleOnSubmit)}
106106
className="flex flex-col gap-2"
107107
>
108+
{JSON.stringify(form.formState.errors)}
109+
108110
<FormField
109111
control={form.control}
110112
name="handle"

0 commit comments

Comments
 (0)