Skip to content

Commit 85f4fc7

Browse files
committed
refactor: remove MemoContentContext and integrate MemoViewContext
- Deleted MemoContentContext and its associated types. - Updated Tag and TaskListItem components to use MemoViewContext instead. - Refactored MemoContent component to eliminate context provider and directly use derived values. - Simplified MemoViewContext to only include essential data. - Enhanced error handling in various components by introducing a centralized error handling utility. - Improved type safety across components and hooks by refining TypeScript definitions. - Updated remark plugins to enhance tag parsing and preserve node types.
1 parent ab650ac commit 85f4fc7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+455
-396
lines changed

web/biome.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@
8484
"noDuplicateObjectKeys": "error",
8585
"noDuplicateParameters": "error",
8686
"noEmptyBlockStatements": "off",
87-
"noExplicitAny": "off",
87+
"noExplicitAny": "error",
8888
"noExtraNonNullAssertion": "error",
8989
"noFallthroughSwitchClause": "error",
9090
"noFunctionAssign": "error",

web/src/components/ChangeMemberPasswordDialog.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "
55
import { Input } from "@/components/ui/input";
66
import { Label } from "@/components/ui/label";
77
import { useUpdateUser } from "@/hooks/useUserQueries";
8+
import { handleError } from "@/lib/error";
89
import { User } from "@/types/proto/api/v1/user_service_pb";
910
import { useTranslate } from "@/utils/i18n";
1011

@@ -60,9 +61,10 @@ function ChangeMemberPasswordDialog({ open, onOpenChange, user, onSuccess }: Pro
6061
toast(t("message.password-changed"));
6162
onSuccess?.();
6263
onOpenChange(false);
63-
} catch (error: any) {
64-
console.error(error);
65-
toast.error(error.message);
64+
} catch (error: unknown) {
65+
await handleError(error, toast.error, {
66+
context: "Change member password",
67+
});
6668
}
6769
};
6870

web/src/components/CreateAccessTokenDialog.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
88
import { userServiceClient } from "@/connect";
99
import useCurrentUser from "@/hooks/useCurrentUser";
1010
import useLoading from "@/hooks/useLoading";
11+
import { handleError } from "@/lib/error";
1112
import { CreatePersonalAccessTokenResponse } from "@/types/proto/api/v1/user_service_pb";
1213
import { useTranslate } from "@/utils/i18n";
1314

@@ -83,10 +84,11 @@ function CreateAccessTokenDialog({ open, onOpenChange, onSuccess }: Props) {
8384
requestState.setFinish();
8485
onSuccess(response);
8586
onOpenChange(false);
86-
} catch (error: any) {
87-
toast.error(error.message);
88-
console.error(error);
89-
requestState.setError();
87+
} catch (error: unknown) {
88+
handleError(error, toast.error, {
89+
context: "Create access token",
90+
onError: () => requestState.setError(),
91+
});
9092
}
9193
};
9294

web/src/components/CreateIdentityProviderDialog.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
99
import { Separator } from "@/components/ui/separator";
1010
import { identityProviderServiceClient } from "@/connect";
1111
import { absolutifyLink } from "@/helpers/utils";
12+
import { handleError } from "@/lib/error";
1213
import {
1314
FieldMapping,
1415
FieldMappingSchema,
@@ -288,9 +289,10 @@ function CreateIdentityProviderDialog({ open, onOpenChange, identityProvider, on
288289
});
289290
toast.success(t("setting.sso-section.sso-updated", { name: basicInfo.title }));
290291
}
291-
} catch (error: any) {
292-
toast.error(error.message);
293-
console.error(error);
292+
} catch (error: unknown) {
293+
await handleError(error, toast.error, {
294+
context: isCreating ? "Create identity provider" : "Update identity provider",
295+
});
294296
}
295297
onSuccess?.();
296298
onOpenChange(false);

web/src/components/CreateShortcutDialog.tsx

Lines changed: 29 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { create } from "@bufbuild/protobuf";
22
import { FieldMaskSchema } from "@bufbuild/protobuf/wkt";
3-
import React, { useEffect, useState } from "react";
3+
import { useEffect, useState } from "react";
44
import { toast } from "react-hot-toast";
55
import { Button } from "@/components/ui/button";
66
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
@@ -11,6 +11,7 @@ import { shortcutServiceClient } from "@/connect";
1111
import { useAuth } from "@/contexts/AuthContext";
1212
import useCurrentUser from "@/hooks/useCurrentUser";
1313
import useLoading from "@/hooks/useLoading";
14+
import { handleError } from "@/lib/error";
1415
import { Shortcut, ShortcutSchema } from "@/types/proto/api/v1/shortcut_service_pb";
1516
import { useTranslate } from "@/utils/i18n";
1617

@@ -33,31 +34,34 @@ function CreateShortcutDialog({ open, onOpenChange, shortcut: initialShortcut, o
3334
}),
3435
);
3536
const requestState = useLoading(false);
36-
const isCreating = !initialShortcut;
37+
const isCreating = shortcut.name === "";
3738

3839
useEffect(() => {
39-
if (initialShortcut) {
40-
setShortcut(
41-
create(ShortcutSchema, {
42-
name: initialShortcut.name,
43-
title: initialShortcut.title,
44-
filter: initialShortcut.filter,
45-
}),
46-
);
47-
} else {
48-
setShortcut(create(ShortcutSchema, { name: "", title: "", filter: "" }));
40+
if (shortcut.name) {
41+
setShortcut(shortcut);
4942
}
50-
}, [initialShortcut]);
43+
}, [shortcut.name, shortcut.title, shortcut.filter]);
5144

5245
const onShortcutTitleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
53-
setShortcut({ ...shortcut, title: e.target.value });
46+
setPartialState({
47+
title: e.target.value,
48+
});
5449
};
5550

5651
const onShortcutFilterChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
57-
setShortcut({ ...shortcut, filter: e.target.value });
52+
setPartialState({
53+
filter: e.target.value,
54+
});
5855
};
5956

60-
const handleConfirm = async () => {
57+
const setPartialState = (partialState: Partial<Shortcut>) => {
58+
setShortcut({
59+
...shortcut,
60+
...partialState,
61+
});
62+
};
63+
64+
const handleSaveBtnClick = async () => {
6165
if (!shortcut.title || !shortcut.filter) {
6266
toast.error("Title and filter cannot be empty");
6367
return;
@@ -69,7 +73,7 @@ function CreateShortcutDialog({ open, onOpenChange, shortcut: initialShortcut, o
6973
await shortcutServiceClient.createShortcut({
7074
parent: user?.name,
7175
shortcut: {
72-
name: "", // Will be set by server
76+
name: "",
7377
title: shortcut.title,
7478
filter: shortcut.filter,
7579
},
@@ -79,21 +83,21 @@ function CreateShortcutDialog({ open, onOpenChange, shortcut: initialShortcut, o
7983
await shortcutServiceClient.updateShortcut({
8084
shortcut: {
8185
...shortcut,
82-
name: initialShortcut!.name, // Keep the original resource name
86+
name: initialShortcut!.name,
8387
},
8488
updateMask: create(FieldMaskSchema, { paths: ["title", "filter"] }),
8589
});
8690
toast.success("Update shortcut successfully");
8791
}
88-
// Refresh shortcuts.
8992
await refetchSettings();
9093
requestState.setFinish();
9194
onSuccess?.();
9295
onOpenChange(false);
93-
} catch (error: any) {
94-
console.error(error);
95-
toast.error(error.message);
96-
requestState.setError();
96+
} catch (error: unknown) {
97+
await handleError(error, toast.error, {
98+
context: isCreating ? "Create shortcut" : "Update shortcut",
99+
onError: () => requestState.setError(),
100+
});
97101
}
98102
};
99103

@@ -118,28 +122,13 @@ function CreateShortcutDialog({ open, onOpenChange, shortcut: initialShortcut, o
118122
onChange={onShortcutFilterChange}
119123
/>
120124
</div>
121-
<div className="text-sm text-muted-foreground">
122-
<p className="mb-2">{t("common.learn-more")}:</p>
123-
<ul className="list-disc list-inside space-y-1">
124-
<li>
125-
<a
126-
className="text-primary hover:underline"
127-
href="https://www.usememos.com/docs/guides/shortcuts"
128-
target="_blank"
129-
rel="noopener noreferrer"
130-
>
131-
Docs - Shortcuts
132-
</a>
133-
</li>
134-
</ul>
135-
</div>
136125
</div>
137126
<DialogFooter>
138127
<Button variant="ghost" disabled={requestState.isLoading} onClick={() => onOpenChange(false)}>
139128
{t("common.cancel")}
140129
</Button>
141-
<Button disabled={requestState.isLoading} onClick={handleConfirm}>
142-
{t("common.confirm")}
130+
<Button disabled={requestState.isLoading} onClick={handleSaveBtnClick}>
131+
{t("common.save")}
143132
</Button>
144133
</DialogFooter>
145134
</DialogContent>

web/src/components/CreateUserDialog.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Label } from "@/components/ui/label";
99
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
1010
import { userServiceClient } from "@/connect";
1111
import useLoading from "@/hooks/useLoading";
12+
import { handleError } from "@/lib/error";
1213
import { User, User_Role, UserSchema } from "@/types/proto/api/v1/user_service_pb";
1314
import { useTranslate } from "@/utils/i18n";
1415

@@ -68,10 +69,11 @@ function CreateUserDialog({ open, onOpenChange, user: initialUser, onSuccess }:
6869
requestState.setFinish();
6970
onSuccess?.();
7071
onOpenChange(false);
71-
} catch (error: any) {
72-
console.error(error);
73-
toast.error(error.message);
74-
requestState.setError();
72+
} catch (error: unknown) {
73+
handleError(error, toast.error, {
74+
context: user ? "Update user" : "Create user",
75+
onError: () => requestState.setError(),
76+
});
7577
}
7678
};
7779

web/src/components/CreateWebhookDialog.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Label } from "@/components/ui/label";
99
import { userServiceClient } from "@/connect";
1010
import useCurrentUser from "@/hooks/useCurrentUser";
1111
import useLoading from "@/hooks/useLoading";
12+
import { handleError } from "@/lib/error";
1213
import { useTranslate } from "@/utils/i18n";
1314

1415
interface Props {
@@ -107,10 +108,11 @@ function CreateWebhookDialog({ open, onOpenChange, webhookName, onSuccess }: Pro
107108
onSuccess?.();
108109
onOpenChange(false);
109110
requestState.setFinish();
110-
} catch (error: any) {
111-
console.error(error);
112-
toast.error(error.message);
113-
requestState.setError();
111+
} catch (error: unknown) {
112+
handleError(error, toast.error, {
113+
context: webhookName ? "Update webhook" : "Create webhook",
114+
onError: () => requestState.setError(),
115+
});
114116
}
115117
};
116118

web/src/components/Inbox/MemoCommentMessage.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { activityServiceClient, memoServiceClient, userServiceClient } from "@/c
88
import { activityNamePrefix } from "@/helpers/resource-names";
99
import useAsyncEffect from "@/hooks/useAsyncEffect";
1010
import useNavigateTo from "@/hooks/useNavigateTo";
11+
import { handleError } from "@/lib/error";
1112
import { cn } from "@/lib/utils";
1213
import { Memo } from "@/types/proto/api/v1/memo_service_pb";
1314
import { User, UserNotification, UserNotification_Status } from "@/types/proto/api/v1/user_service_pb";
@@ -56,8 +57,10 @@ function MemoCommentMessage({ notification }: Props) {
5657
setInitialized(true);
5758
}
5859
} catch (error) {
59-
console.error("Failed to fetch activity:", error);
60-
setHasError(true);
60+
handleError(error, () => {}, {
61+
context: "Failed to fetch activity",
62+
onError: () => setHasError(true),
63+
});
6164
return;
6265
}
6366
}, [notification.activityId]);

web/src/components/MemoActionMenu/hooks.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { useInstance } from "@/contexts/InstanceContext";
77
import { useDeleteMemo, useUpdateMemo } from "@/hooks/useMemoQueries";
88
import useNavigateTo from "@/hooks/useNavigateTo";
99
import { userKeys } from "@/hooks/useUserQueries";
10+
import { handleError } from "@/lib/error";
1011
import { State } from "@/types/proto/api/v1/common_pb";
1112
import type { Memo } from "@/types/proto/api/v1/memo_service_pb";
1213
import { useTranslate } from "@/utils/i18n";
@@ -53,6 +54,7 @@ export const useMemoActionHandlers = ({ memo, onEdit, setDeleteDialogOpen, setRe
5354
}, [onEdit]);
5455

5556
const handleToggleMemoStatusClick = useCallback(async () => {
57+
const isArchiving = memo.state !== State.ARCHIVED;
5658
const state = memo.state === State.ARCHIVED ? State.NORMAL : State.ARCHIVED;
5759
const message = memo.state === State.ARCHIVED ? t("message.restored-successfully") : t("message.archived-successfully");
5860

@@ -66,9 +68,10 @@ export const useMemoActionHandlers = ({ memo, onEdit, setDeleteDialogOpen, setRe
6668
});
6769
toast.success(message);
6870
} catch (error: unknown) {
69-
const err = error as { details?: string };
70-
toast.error(err.details || "An error occurred");
71-
console.error(error);
71+
handleError(error, toast.error, {
72+
context: `${isArchiving ? "Archive" : "Restore"} memo`,
73+
fallbackMessage: "An error occurred",
74+
});
7275
return;
7376
}
7477

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,22 @@
1+
import type { Element } from "hast";
12
import React from "react";
3+
import { isTagElement, isTaskListItemElement } from "@/types/markdown";
24

3-
export const createConditionalComponent = <P extends Record<string, any>>(
5+
/**
6+
* Creates a conditional component that renders different components
7+
* based on AST node type detection
8+
*
9+
* @param CustomComponent - Custom component to render when condition matches
10+
* @param DefaultComponent - Default component/element to render otherwise
11+
* @param condition - Function to test AST node
12+
* @returns Conditional wrapper component
13+
*/
14+
export const createConditionalComponent = <P extends Record<string, unknown>>(
415
CustomComponent: React.ComponentType<P>,
516
DefaultComponent: React.ComponentType<P> | keyof JSX.IntrinsicElements,
6-
condition: (node: any) => boolean,
17+
condition: (node: Element) => boolean,
718
) => {
8-
return (props: P & { node?: any }) => {
19+
return (props: P & { node?: Element }) => {
920
const { node, ...restProps } = props;
1021

1122
// Check AST node to determine which component to use
@@ -21,17 +32,5 @@ export const createConditionalComponent = <P extends Record<string, any>>(
2132
};
2233
};
2334

24-
// Condition checkers for AST node types
25-
export const isTagNode = (node: any): boolean => {
26-
// Check preserved mdast type first
27-
if (node?.data?.mdastType === "tagNode") {
28-
return true;
29-
}
30-
// Fallback: check hast properties
31-
return node?.properties?.className?.includes?.("tag") || false;
32-
};
33-
34-
export const isTaskListItemNode = (node: any): boolean => {
35-
// Task list checkboxes are standard GFM - check element type
36-
return node?.properties?.type === "checkbox" || false;
37-
};
35+
// Re-export type guards for convenience
36+
export { isTagElement as isTagNode, isTaskListItemElement as isTaskListItemNode };

0 commit comments

Comments
 (0)