diff --git a/apps/renderer/src/modules/command/commands/entry.tsx b/apps/renderer/src/modules/command/commands/entry.tsx
index d49e3495d2..b6a14465fe 100644
--- a/apps/renderer/src/modules/command/commands/entry.tsx
+++ b/apps/renderer/src/modules/command/commands/entry.tsx
@@ -13,8 +13,7 @@ import { tipcClient } from "~/lib/client"
import { useTipModal } from "~/modules/wallet/hooks"
import { entryActions, useEntryStore } from "~/store/entry"
-import { useRegisterCommandEffect } from "../hooks/use-register-command-effect"
-import { defineFollowCommand } from "../registry/command"
+import { useRegisterFollowCommand } from "../hooks/use-register-command"
import { COMMAND_ID } from "./id"
const useCollect = () => {
@@ -89,8 +88,8 @@ export const useRegisterEntryCommands = () => {
const read = useRead()
const unread = useUnread()
- useRegisterCommandEffect([
- defineFollowCommand({
+ useRegisterFollowCommand([
+ {
id: COMMAND_ID.entry.tip,
label: t("entry_actions.tip"),
icon: ,
@@ -105,8 +104,8 @@ export const useRegisterEntryCommands = () => {
}),
)
},
- }),
- defineFollowCommand({
+ },
+ {
id: COMMAND_ID.entry.star,
label: t("entry_actions.star"),
icon: ,
@@ -127,8 +126,8 @@ export const useRegisterEntryCommands = () => {
// }
collect.mutate({ entryId, view })
},
- }),
- defineFollowCommand({
+ },
+ {
id: COMMAND_ID.entry.unstar,
label: t("entry_actions.unstar"),
icon: ,
@@ -140,8 +139,8 @@ export const useRegisterEntryCommands = () => {
}
uncollect.mutate(entry.entries.id)
},
- }),
- defineFollowCommand({
+ },
+ {
id: COMMAND_ID.entry.delete,
label: t("entry_actions.delete"),
icon: ,
@@ -153,8 +152,8 @@ export const useRegisterEntryCommands = () => {
}
deleteInboxEntry.mutate(entry.entries.id)
},
- }),
- defineFollowCommand({
+ },
+ {
id: COMMAND_ID.entry.copyLink,
label: t("entry_actions.copy_link"),
icon: ,
@@ -170,8 +169,8 @@ export const useRegisterEntryCommands = () => {
duration: 1000,
})
},
- }),
- defineFollowCommand({
+ },
+ {
id: COMMAND_ID.entry.copyTitle,
label: t("entry_actions.copy_title"),
icon: ,
@@ -187,8 +186,8 @@ export const useRegisterEntryCommands = () => {
duration: 1000,
})
},
- }),
- defineFollowCommand({
+ },
+ {
id: COMMAND_ID.entry.openInBrowser,
label: t("entry_actions.open_in_browser", {
which: t(IN_ELECTRON ? "words.browser" : "words.newTab"),
@@ -202,8 +201,8 @@ export const useRegisterEntryCommands = () => {
}
window.open(entry.entries.url, "_blank")
},
- }),
- defineFollowCommand({
+ },
+ {
id: COMMAND_ID.entry.viewSourceContent,
label: t("entry_actions.view_source_content"),
icon: ,
@@ -232,16 +231,16 @@ export const useRegisterEntryCommands = () => {
}
setShowSourceContent(true)
},
- }),
- defineFollowCommand({
+ },
+ {
id: COMMAND_ID.entry.viewEntryContent,
label: t("entry_actions.view_source_content"),
icon: ,
run: () => {
setShowSourceContent(false)
},
- }),
- defineFollowCommand({
+ },
+ {
id: COMMAND_ID.entry.share,
label: t("entry_actions.share"),
icon:
@@ -267,8 +266,8 @@ export const useRegisterEntryCommands = () => {
}
return
},
- }),
- defineFollowCommand({
+ },
+ {
id: COMMAND_ID.entry.read,
label: t("entry_actions.mark_as_read"),
icon: ,
@@ -280,8 +279,8 @@ export const useRegisterEntryCommands = () => {
}
read.mutate({ entryId, feedId: entry.feedId })
},
- }),
- defineFollowCommand({
+ },
+ {
id: COMMAND_ID.entry.unread,
label: t("entry_actions.mark_as_unread"),
icon: ,
@@ -293,6 +292,6 @@ export const useRegisterEntryCommands = () => {
}
unread.mutate({ entryId, feedId: entry.feedId })
},
- }),
+ },
])
}
diff --git a/apps/renderer/src/modules/command/commands/integration.tsx b/apps/renderer/src/modules/command/commands/integration.tsx
index 87595f26c2..3a656164a0 100644
--- a/apps/renderer/src/modules/command/commands/integration.tsx
+++ b/apps/renderer/src/modules/command/commands/integration.tsx
@@ -21,7 +21,7 @@ import { parseHtml } from "~/lib/parse-html"
import type { FlatEntryModel } from "~/store/entry"
import { useEntryStore } from "~/store/entry"
-import { useRegisterCommandEffect } from "../hooks/use-register-command-effect"
+import { useRegisterCommandEffect } from "../hooks/use-register-command"
import { defineFollowCommand } from "../registry/command"
import { COMMAND_ID } from "./id"
diff --git a/apps/renderer/src/modules/command/commands/list.tsx b/apps/renderer/src/modules/command/commands/list.tsx
index 77e69e99a5..17d40c477a 100644
--- a/apps/renderer/src/modules/command/commands/list.tsx
+++ b/apps/renderer/src/modules/command/commands/list.tsx
@@ -9,7 +9,7 @@ import { getRouteParams } from "~/hooks/biz/useRouteParams"
import { useDeleteSubscription } from "~/hooks/biz/useSubscriptionActions"
import { ListForm } from "~/modules/discover/list-form"
-import { useRegisterCommandEffect } from "../hooks/use-register-command-effect"
+import { useRegisterCommandEffect } from "../hooks/use-register-command"
import { COMMAND_ID } from "./id"
export const useRegisterListCommands = () => {
diff --git a/apps/renderer/src/modules/command/commands/theme.tsx b/apps/renderer/src/modules/command/commands/theme.tsx
index 4fa2c8aa2b..8757b503cb 100644
--- a/apps/renderer/src/modules/command/commands/theme.tsx
+++ b/apps/renderer/src/modules/command/commands/theme.tsx
@@ -3,7 +3,7 @@ import { useTranslation } from "react-i18next"
import { useSetTheme } from "~/hooks/common"
-import { useRegisterCommandEffect } from "../hooks/use-register-command-effect"
+import { useRegisterCommandEffect } from "../hooks/use-register-command"
export const useRegisterThemeCommands = () => {
const [t] = useTranslation("settings")
diff --git a/apps/renderer/src/modules/command/hooks/use-register-command-effect.ts b/apps/renderer/src/modules/command/hooks/use-register-command-effect.ts
deleted file mode 100644
index f7c5ff114b..0000000000
--- a/apps/renderer/src/modules/command/hooks/use-register-command-effect.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { useEffect } from "react"
-
-import { registerCommand } from "../registry/registry"
-import type { CommandOptions } from "../types"
-
-export type RegisterOptions = {
- deps?: unknown[]
- enabled?: boolean
- // forceMountSection?: boolean
- // sectionMeta?: Record
- // orderSection?: OrderSectionInstruction
- // orderCommands?: OrderCommandsInstruction
-}
-
-export const useRegisterCommandEffect = (
- options: CommandOptions | CommandOptions[],
- registerOptions?: RegisterOptions,
-) => {
- // TODO memo command via useMemo
- // See https://github.com/supabase/supabase/blob/master/packages/ui-patterns/CommandMenu/api/hooks/commandsHooks.ts
-
- useEffect(() => {
- if (!Array.isArray(options)) {
- return registerCommand(options)
- }
-
- const unsubscribes = options.map((option) => registerCommand(option))
- return () => {
- unsubscribes.forEach((unsubscribe) => unsubscribe())
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps -- we want to run this effect only once
- }, registerOptions?.deps ?? [])
-}
diff --git a/apps/renderer/src/modules/command/hooks/use-register-command.test-d.ts b/apps/renderer/src/modules/command/hooks/use-register-command.test-d.ts
new file mode 100644
index 0000000000..c4264195cc
--- /dev/null
+++ b/apps/renderer/src/modules/command/hooks/use-register-command.test-d.ts
@@ -0,0 +1,83 @@
+import { assertType, expectTypeOf, test } from "vitest"
+
+import { COMMAND_ID } from "../commands/id"
+import { useRegisterFollowCommand } from "./use-register-command"
+
+test("useRegisterFollowCommand types", () => {
+ assertType(
+ useRegisterFollowCommand({
+ id: COMMAND_ID.entry.openInBrowser,
+ label: "",
+ run: ({ entryId }) => {
+ expectTypeOf(entryId).toEqualTypeOf()
+ },
+ }),
+ )
+
+ assertType(
+ useRegisterFollowCommand({
+ id: "unknown id",
+ label: "",
+ run: (...args) => {
+ expectTypeOf(args).toEqualTypeOf<[]>()
+ },
+ }),
+ )
+
+ assertType(
+ useRegisterFollowCommand([
+ {
+ id: COMMAND_ID.entry.star,
+ label: "",
+ run: ({ entryId }) => {
+ expectTypeOf(entryId).toEqualTypeOf()
+ },
+ },
+ {
+ id: COMMAND_ID.entry.viewEntryContent,
+ label: "",
+ run: (...args) => {
+ expectTypeOf(args).toEqualTypeOf<[]>()
+ },
+ },
+ ]),
+ )
+
+ assertType(
+ useRegisterFollowCommand([
+ {
+ id: "unknown id",
+ label: "",
+ run: (...args) => {
+ expectTypeOf(args).toEqualTypeOf<[]>()
+ },
+ },
+ ]),
+ )
+
+ assertType(
+ useRegisterFollowCommand([
+ {
+ id: "unknown id",
+ label: "",
+ run: (...args) => {
+ expectTypeOf(args).toEqualTypeOf<[]>()
+ },
+ },
+ {
+ id: COMMAND_ID.entry.star,
+ label: "",
+ run: ({ entryId }) => {
+ expectTypeOf(entryId).toEqualTypeOf()
+ },
+ },
+ {
+ id: COMMAND_ID.entry.viewEntryContent,
+ label: "",
+ run: (...args) => {
+ expectTypeOf(args).toEqualTypeOf<[]>()
+ },
+ },
+ ]),
+ )
+})
diff --git a/apps/renderer/src/modules/command/hooks/use-register-command.ts b/apps/renderer/src/modules/command/hooks/use-register-command.ts
new file mode 100644
index 0000000000..6eeb9f6ec4
--- /dev/null
+++ b/apps/renderer/src/modules/command/hooks/use-register-command.ts
@@ -0,0 +1,66 @@
+import { useEffect } from "react"
+import { useTranslation } from "react-i18next"
+
+import { registerCommand } from "../registry/registry"
+import type { CommandOptions, FollowCommandId, FollowCommandMap } from "../types"
+
+export type RegisterOptions = {
+ deps?: unknown[]
+ enabled?: boolean
+ // forceMountSection?: boolean
+ // sectionMeta?: Record
+ // orderSection?: OrderSectionInstruction
+ // orderCommands?: OrderCommandsInstruction
+}
+
+export const useRegisterCommandEffect = (
+ options: CommandOptions | CommandOptions[],
+ registerOptions?: RegisterOptions,
+) => {
+ const { t } = useTranslation()
+ useEffect(() => {
+ if (!Array.isArray(options)) {
+ return registerCommand(options)
+ }
+
+ const unsubscribes = options.map((option) => registerCommand(option))
+ return () => {
+ unsubscribes.forEach((unsubscribe) => unsubscribe())
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- we want to run this effect only once
+ }, [t, ...(registerOptions?.deps ?? [])])
+}
+
+/**
+ * Register a follow command.
+ */
+export function useRegisterFollowCommand(
+ options: CommandOptions<{ id: T; fn: FollowCommandMap[T]["run"] }>,
+ registerOptions?: RegisterOptions,
+): void
+/**
+ * Register a unknown command.
+ */
+export function useRegisterFollowCommand(
+ options: CommandOptions<{ id: T; fn: () => void }>,
+ registerOptions?: RegisterOptions,
+): void
+/**
+ * Register multiple follow commands or unknown commands.
+ */
+export function useRegisterFollowCommand(
+ options: [
+ ...{
+ [K in keyof T]: T[K] extends FollowCommandId
+ ? CommandOptions<{ id: T[K]; fn: FollowCommandMap[T[K]]["run"] }>
+ : CommandOptions<{ id: T[K]; fn: () => void }>
+ },
+ ],
+ registerOptions?: RegisterOptions,
+): void
+export function useRegisterFollowCommand(
+ options: CommandOptions | CommandOptions[],
+ registerOptions?: RegisterOptions,
+) {
+ return useRegisterCommandEffect(options as CommandOptions | CommandOptions[], registerOptions)
+}
diff --git a/apps/renderer/src/modules/command/registry/command.test-d.ts b/apps/renderer/src/modules/command/registry/command.test-d.ts
index 4627ba6385..ec00d544c7 100644
--- a/apps/renderer/src/modules/command/registry/command.test-d.ts
+++ b/apps/renderer/src/modules/command/registry/command.test-d.ts
@@ -1,7 +1,7 @@
import { assertType, expectTypeOf, test } from "vitest"
import { COMMAND_ID } from "../commands/id"
-import { defineCommandArgsArray, defineFollowCommand, defineFollowCommandArgs } from "./command"
+import { defineFollowCommand } from "./command"
test("defineFollowCommand types", () => {
assertType(
@@ -68,71 +68,3 @@ test("defineFollowCommand with keyBinding types", () => {
}),
)
})
-
-test("defineCommandArgs with keyBinding types", () => {
- assertType(
- defineFollowCommandArgs({
- commandId: COMMAND_ID.entry.star,
- args: [{ entryId: "1" }],
- }),
- )
-
- assertType(
- defineFollowCommandArgs({
- commandId: COMMAND_ID.entry.star,
- // @ts-expect-error - invalid args
- args: [],
- }),
- )
-})
-
-test("defineCommandArgsArray with keyBinding types", () => {
- assertType(
- defineCommandArgsArray([
- {
- commandId: COMMAND_ID.entry.star,
- args: [{ entryId: "1" }],
- },
- ]),
- )
-
- assertType(
- defineCommandArgsArray([
- {
- commandId: COMMAND_ID.entry.star,
- // @ts-expect-error - invalid args
- args: [],
- },
- ]),
- )
-
- assertType(
- defineCommandArgsArray([
- {
- commandId: COMMAND_ID.entry.star,
- // @ts-expect-error - invalid args
- args: [],
- },
- {
- commandId: COMMAND_ID.entry.viewEntryContent,
- args: [],
- },
- ]),
- )
-
- assertType(
- defineCommandArgsArray<{ test: boolean }>([
- {
- commandId: COMMAND_ID.entry.star,
- args: [{ entryId: "1" }],
- test: true,
- },
- {
- commandId: COMMAND_ID.entry.viewEntryContent,
- args: [],
- // @ts-expect-error - invalid extra property
- test: 1,
- },
- ]),
- )
-})
diff --git a/apps/renderer/src/modules/command/registry/command.ts b/apps/renderer/src/modules/command/registry/command.ts
index ebea2b9868..de30ab791a 100644
--- a/apps/renderer/src/modules/command/registry/command.ts
+++ b/apps/renderer/src/modules/command/registry/command.ts
@@ -1,4 +1,10 @@
-import type { Command, CommandOptions, FollowCommand, FollowCommandId } from "../types"
+import type {
+ Command,
+ CommandOptions,
+ FollowCommand,
+ FollowCommandId,
+ FollowCommandMap,
+} from "../types"
export function createCommand<
T extends { id: string; fn: (...args: any[]) => unknown } = {
@@ -32,33 +38,7 @@ export function createFollowCommand(
}
export function defineFollowCommand(
- options: CommandOptions<{ id: T; fn: Extract["run"] }>,
+ options: CommandOptions<{ id: T; fn: FollowCommandMap[T]["run"] }>,
) {
return options as CommandOptions
}
-
-/**
- * @deprecated
- */
-export const defineFollowCommandArgs = (config: {
- commandId: T
- args: Parameters["run"]>
-}) => config
-
-/**
- * @deprecated
- */
-export const defineCommandArgsArray = <
- Ext extends Record,
- T extends FollowCommandId[] = FollowCommandId[],
->(
- config: [
- ...{
- [K in keyof T]: {
- commandId: T[K]
- args: Parameters["run"]>
- // [key: string]: unknown
- } & Ext
- },
- ],
-) => config