Skip to content

Commit c4ace25

Browse files
committed
refactor: simplify docs controls and project list query
1 parent 803df73 commit c4ace25

5 files changed

Lines changed: 105 additions & 88 deletions

File tree

‎components/docs-project-switcher.tsx‎

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { useEffect, useMemo, useRef, useState, useSyncExternalStore, useTransition } from "react";
44
import { useRouter } from "next/navigation";
5+
import { useDismissablePopover } from "@/components/use-dismissable-popover";
56
import { localizedPath, type Locale } from "@/lib/locales";
67

78
const STORAGE_KEY = "markapi:visited-projects";
@@ -147,33 +148,10 @@ export function DocsProjectSwitcher({ currentProject, labels, locale }: DocsProj
147148
rememberProject({ name, token });
148149
}, [name, token]);
149150

150-
useEffect(() => {
151-
if (!isOpen) {
152-
return;
153-
}
154-
155-
function handlePointerDown(event: PointerEvent) {
156-
if (!switcherRef.current?.contains(event.target as Node)) {
157-
setIsOpen(false);
158-
setConfirmingToken(null);
159-
}
160-
}
161-
162-
function handleKeyDown(event: KeyboardEvent) {
163-
if (event.key === "Escape") {
164-
setIsOpen(false);
165-
setConfirmingToken(null);
166-
}
167-
}
168-
169-
document.addEventListener("pointerdown", handlePointerDown);
170-
document.addEventListener("keydown", handleKeyDown);
171-
172-
return () => {
173-
document.removeEventListener("pointerdown", handlePointerDown);
174-
document.removeEventListener("keydown", handleKeyDown);
175-
};
176-
}, [isOpen]);
151+
useDismissablePopover(isOpen, switcherRef, () => {
152+
setIsOpen(false);
153+
setConfirmingToken(null);
154+
});
177155

178156
return (
179157
<div className="project-switcher" ref={switcherRef}>

‎components/settings-menu.tsx‎

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
import { Check, Monitor, Moon, Settings, Sun } from "lucide-react";
44
import type { LucideIcon } from "lucide-react";
55
import { usePathname, useRouter, useSearchParams } from "next/navigation";
6-
import { type ReactNode, useEffect, useRef, useState, useSyncExternalStore, useTransition } from "react";
6+
import { type ReactNode, useRef, useState, useSyncExternalStore, useTransition } from "react";
7+
import { useDismissablePopover } from "@/components/use-dismissable-popover";
78
import { isLocale, SUPPORTED_LOCALES, type Locale } from "@/lib/locales";
89
import type { Messages } from "@/lib/messages";
910

@@ -102,31 +103,7 @@ export function SettingsMenu({ children, labels, locale }: SettingsMenuProps) {
102103
const [isOpen, setIsOpen] = useState(false);
103104
const [isPending, startTransition] = useTransition();
104105

105-
useEffect(() => {
106-
if (!isOpen) {
107-
return;
108-
}
109-
110-
function handlePointerDown(event: PointerEvent) {
111-
if (!menuRef.current?.contains(event.target as Node)) {
112-
setIsOpen(false);
113-
}
114-
}
115-
116-
function handleKeyDown(event: KeyboardEvent) {
117-
if (event.key === "Escape") {
118-
setIsOpen(false);
119-
}
120-
}
121-
122-
document.addEventListener("pointerdown", handlePointerDown);
123-
document.addEventListener("keydown", handleKeyDown);
124-
125-
return () => {
126-
document.removeEventListener("pointerdown", handlePointerDown);
127-
document.removeEventListener("keydown", handleKeyDown);
128-
};
129-
}, [isOpen]);
106+
useDismissablePopover(isOpen, menuRef, () => setIsOpen(false));
130107

131108
function switchLocale(nextLocale: Locale) {
132109
if (nextLocale === locale) {
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"use client";
2+
3+
import { useEffect, type RefObject } from "react";
4+
5+
export function useDismissablePopover<T extends HTMLElement>(
6+
isOpen: boolean,
7+
containerRef: RefObject<T | null>,
8+
onDismiss: () => void
9+
) {
10+
useEffect(() => {
11+
if (!isOpen) {
12+
return;
13+
}
14+
15+
function handlePointerDown(event: PointerEvent) {
16+
if (!containerRef.current?.contains(event.target as Node)) {
17+
onDismiss();
18+
}
19+
}
20+
21+
function handleKeyDown(event: KeyboardEvent) {
22+
if (event.key === "Escape") {
23+
onDismiss();
24+
}
25+
}
26+
27+
document.addEventListener("pointerdown", handlePointerDown);
28+
document.addEventListener("keydown", handleKeyDown);
29+
30+
return () => {
31+
document.removeEventListener("pointerdown", handlePointerDown);
32+
document.removeEventListener("keydown", handleKeyDown);
33+
};
34+
}, [containerRef, isOpen, onDismiss]);
35+
}

‎components/version-select.tsx‎

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
"use client";
22

33
import { useRouter } from "next/navigation";
4-
import { useEffect, useRef, useState, useTransition } from "react";
4+
import { useRef, useState, useTransition } from "react";
5+
import { useDismissablePopover } from "@/components/use-dismissable-popover";
56
import { localizedPath, type Locale } from "@/lib/locales";
67

78
type VersionOption = {
@@ -38,31 +39,7 @@ export function VersionSelect({
3839
const selectedVersion = versions.find((version) => version.id === selectedVersionId);
3940
const isLoading = isPending || pendingVersionId !== null;
4041

41-
useEffect(() => {
42-
if (!isOpen) {
43-
return;
44-
}
45-
46-
function handlePointerDown(event: PointerEvent) {
47-
if (!versionSelectRef.current?.contains(event.target as Node)) {
48-
setIsOpen(false);
49-
}
50-
}
51-
52-
function handleKeyDown(event: KeyboardEvent) {
53-
if (event.key === "Escape") {
54-
setIsOpen(false);
55-
}
56-
}
57-
58-
document.addEventListener("pointerdown", handlePointerDown);
59-
document.addEventListener("keydown", handleKeyDown);
60-
61-
return () => {
62-
document.removeEventListener("pointerdown", handlePointerDown);
63-
document.removeEventListener("keydown", handleKeyDown);
64-
};
65-
}, [isOpen]);
42+
useDismissablePopover(isOpen, versionSelectRef, () => setIsOpen(false));
6643

6744
function openVersion(versionId: number) {
6845
if (versionId === currentVersionId) {

‎lib/db.ts‎

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,23 @@ type DocumentVersionRow = {
2121
createdAt: number;
2222
};
2323

24+
type ProjectWithLatestVersionRow = ProjectRow & (
25+
| {
26+
versionId: null;
27+
versionProjectId: null;
28+
versionContent: null;
29+
versionNote: null;
30+
versionCreatedAt: null;
31+
}
32+
| {
33+
versionId: number;
34+
versionProjectId: number;
35+
versionContent: string;
36+
versionNote: string | null;
37+
versionCreatedAt: number;
38+
}
39+
);
40+
2441
export type Project = {
2542
id: number;
2643
name: string;
@@ -242,16 +259,49 @@ export function getProjectWithVersionsByShareToken(shareToken: string): ProjectW
242259
export function getProjectsWithLatestVersion(): ProjectWithVersions[] {
243260
const projects = getDb()
244261
.prepare(`
245-
SELECT "id", "name", "slug", "shareToken", "allowPublicVersionHistory", "createdAt", "updatedAt"
246-
FROM "Project"
247-
ORDER BY "createdAt" DESC
262+
SELECT
263+
p."id",
264+
p."name",
265+
p."slug",
266+
p."shareToken",
267+
p."allowPublicVersionHistory",
268+
p."createdAt",
269+
p."updatedAt",
270+
v."id" AS "versionId",
271+
v."projectId" AS "versionProjectId",
272+
v."content" AS "versionContent",
273+
v."note" AS "versionNote",
274+
v."createdAt" AS "versionCreatedAt"
275+
FROM "Project" p
276+
LEFT JOIN "DocumentVersion" v ON v."id" = (
277+
SELECT latest."id"
278+
FROM "DocumentVersion" latest
279+
WHERE latest."projectId" = p."id"
280+
ORDER BY latest."createdAt" DESC
281+
LIMIT 1
282+
)
283+
ORDER BY p."createdAt" DESC
248284
`)
249-
.all() as ProjectRow[];
250-
251-
return projects.map((project) => ({
252-
...toProject(project),
253-
versions: getProjectVersions(project.id, 1)
254-
}));
285+
.all() as ProjectWithLatestVersionRow[];
286+
287+
return projects.map((project) => {
288+
const versions = project.versionId === null
289+
? []
290+
: [
291+
toDocumentVersion({
292+
id: project.versionId,
293+
projectId: project.versionProjectId,
294+
content: project.versionContent,
295+
note: project.versionNote,
296+
createdAt: project.versionCreatedAt
297+
})
298+
];
299+
300+
return {
301+
...toProject(project),
302+
versions
303+
};
304+
});
255305
}
256306

257307
export function getProjectWithVersionsById(id: number): ProjectWithVersions | null {

0 commit comments

Comments
 (0)