diff --git a/packages/extension/public/_locales/de/messages.json b/packages/extension/public/_locales/de/messages.json index 83abb1575..cc136a2c2 100644 --- a/packages/extension/public/_locales/de/messages.json +++ b/packages/extension/public/_locales/de/messages.json @@ -749,6 +749,9 @@ "Option_shareButton_tooltip": { "message": "Mit Hub teilen" }, + "Option_shareButton_shared_tooltip": { + "message": "Bereits geteilt" + }, "Option_remove_title": { "message": "Dies löschen?" }, diff --git a/packages/extension/public/_locales/en/messages.json b/packages/extension/public/_locales/en/messages.json index 8740e0642..798a936d4 100644 --- a/packages/extension/public/_locales/en/messages.json +++ b/packages/extension/public/_locales/en/messages.json @@ -753,6 +753,10 @@ "message": "Share to Hub", "description": "Tooltip for the share button in the command list. Opens the new Selection Command Hub in a new tab with the command pre-filled." }, + "Option_shareButton_shared_tooltip": { + "message": "Already shared", + "description": "Tooltip for the share button when the command has already been shared to the Hub. Clicking opens the hub dashboard for this command." + }, "Option_remove_title": { "message": "Delete this?" }, diff --git a/packages/extension/public/_locales/es/messages.json b/packages/extension/public/_locales/es/messages.json index b745b64d9..e6f01735c 100644 --- a/packages/extension/public/_locales/es/messages.json +++ b/packages/extension/public/_locales/es/messages.json @@ -749,6 +749,9 @@ "Option_shareButton_tooltip": { "message": "Compartir en Hub" }, + "Option_shareButton_shared_tooltip": { + "message": "Ya compartido" + }, "Option_remove_title": { "message": "¿Eliminar esto?" }, diff --git a/packages/extension/public/_locales/fr/messages.json b/packages/extension/public/_locales/fr/messages.json index 415c861ba..09bb2ceef 100644 --- a/packages/extension/public/_locales/fr/messages.json +++ b/packages/extension/public/_locales/fr/messages.json @@ -749,6 +749,9 @@ "Option_shareButton_tooltip": { "message": "Partager sur Hub" }, + "Option_shareButton_shared_tooltip": { + "message": "Déjà partagé" + }, "Option_remove_title": { "message": "Supprimer ceci ?" }, diff --git a/packages/extension/public/_locales/hi/messages.json b/packages/extension/public/_locales/hi/messages.json index 022c7eee4..2954e5aa0 100644 --- a/packages/extension/public/_locales/hi/messages.json +++ b/packages/extension/public/_locales/hi/messages.json @@ -749,6 +749,9 @@ "Option_shareButton_tooltip": { "message": "Hub पर साझा करें" }, + "Option_shareButton_shared_tooltip": { + "message": "पहले से साझा किया गया" + }, "Option_remove_title": { "message": "क्या आप वाकई इसे हटाना चाहते हैं?" }, diff --git a/packages/extension/public/_locales/id/messages.json b/packages/extension/public/_locales/id/messages.json index fcb0e4357..122719616 100644 --- a/packages/extension/public/_locales/id/messages.json +++ b/packages/extension/public/_locales/id/messages.json @@ -749,6 +749,9 @@ "Option_shareButton_tooltip": { "message": "Bagikan ke Hub" }, + "Option_shareButton_shared_tooltip": { + "message": "Sudah dibagikan" + }, "Option_remove_title": { "message": "Hapus ini?" }, diff --git a/packages/extension/public/_locales/it/messages.json b/packages/extension/public/_locales/it/messages.json index 121a5b8c1..d212e69af 100644 --- a/packages/extension/public/_locales/it/messages.json +++ b/packages/extension/public/_locales/it/messages.json @@ -749,6 +749,9 @@ "Option_shareButton_tooltip": { "message": "Condividi su Hub" }, + "Option_shareButton_shared_tooltip": { + "message": "Già condiviso" + }, "Option_remove_title": { "message": "Sei sicuro di voler eliminare questo?" }, diff --git a/packages/extension/public/_locales/ja/messages.json b/packages/extension/public/_locales/ja/messages.json index b66e6230d..da8f8f60c 100644 --- a/packages/extension/public/_locales/ja/messages.json +++ b/packages/extension/public/_locales/ja/messages.json @@ -749,6 +749,9 @@ "Option_shareButton_tooltip": { "message": "Hubに共有" }, + "Option_shareButton_shared_tooltip": { + "message": "共有済み" + }, "Option_remove_title": { "message": "削除しますか?" }, diff --git a/packages/extension/public/_locales/ko/messages.json b/packages/extension/public/_locales/ko/messages.json index bb1d3c694..666c297d3 100644 --- a/packages/extension/public/_locales/ko/messages.json +++ b/packages/extension/public/_locales/ko/messages.json @@ -749,6 +749,9 @@ "Option_shareButton_tooltip": { "message": "Hub에 공유" }, + "Option_shareButton_shared_tooltip": { + "message": "이미 공유됨" + }, "Option_remove_title": { "message": "이것을 삭제하시겠습니까?" }, diff --git a/packages/extension/public/_locales/ms/messages.json b/packages/extension/public/_locales/ms/messages.json index 9a16bd40f..811fdf60b 100644 --- a/packages/extension/public/_locales/ms/messages.json +++ b/packages/extension/public/_locales/ms/messages.json @@ -749,6 +749,9 @@ "Option_shareButton_tooltip": { "message": "Kongsi ke Hub" }, + "Option_shareButton_shared_tooltip": { + "message": "Sudah dikongsi" + }, "Option_remove_title": { "message": "Padam ini?" }, diff --git a/packages/extension/public/_locales/pt_BR/messages.json b/packages/extension/public/_locales/pt_BR/messages.json index 963d13df0..beed63942 100644 --- a/packages/extension/public/_locales/pt_BR/messages.json +++ b/packages/extension/public/_locales/pt_BR/messages.json @@ -749,6 +749,9 @@ "Option_shareButton_tooltip": { "message": "Compartilhar no Hub" }, + "Option_shareButton_shared_tooltip": { + "message": "Já compartilhado" + }, "Option_remove_title": { "message": "Excluir isto?" }, diff --git a/packages/extension/public/_locales/pt_PT/messages.json b/packages/extension/public/_locales/pt_PT/messages.json index aab01ea8e..4b3e8faf5 100644 --- a/packages/extension/public/_locales/pt_PT/messages.json +++ b/packages/extension/public/_locales/pt_PT/messages.json @@ -749,6 +749,9 @@ "Option_shareButton_tooltip": { "message": "Partilhar no Hub" }, + "Option_shareButton_shared_tooltip": { + "message": "Já partilhado" + }, "Option_remove_title": { "message": "Eliminar isto?" }, diff --git a/packages/extension/public/_locales/ru/messages.json b/packages/extension/public/_locales/ru/messages.json index 6efee3cd5..4461e0224 100644 --- a/packages/extension/public/_locales/ru/messages.json +++ b/packages/extension/public/_locales/ru/messages.json @@ -749,6 +749,9 @@ "Option_shareButton_tooltip": { "message": "Поделиться в Hub" }, + "Option_shareButton_shared_tooltip": { + "message": "Уже опубликовано" + }, "Option_remove_title": { "message": "Вы уверены, что хотите удалить это?" }, diff --git a/packages/extension/public/_locales/zh_CN/messages.json b/packages/extension/public/_locales/zh_CN/messages.json index 508d52a04..396268a31 100644 --- a/packages/extension/public/_locales/zh_CN/messages.json +++ b/packages/extension/public/_locales/zh_CN/messages.json @@ -749,6 +749,9 @@ "Option_shareButton_tooltip": { "message": "分享到 Hub" }, + "Option_shareButton_shared_tooltip": { + "message": "已共享" + }, "Option_remove_title": { "message": "您确定要删除吗?" }, diff --git a/packages/extension/src/background_script.ts b/packages/extension/src/background_script.ts index e31d4cd98..8a55577c3 100644 --- a/packages/extension/src/background_script.ts +++ b/packages/extension/src/background_script.ts @@ -4,35 +4,24 @@ import { SHORTCUT_NO_SELECTION_BEHAVIOR, HUB_URL, SCREEN, - COMMAND_SOURCE_TYPE, } from "@/const" import { executeActionProps } from "@/services/contextMenus" import { Ipc, BgCommand, TabCommand, CONNECTION_APP } from "@/services/ipc" import type { IpcCallback } from "@/services/ipc" import { Settings } from "@/services/settings/settings" import { enhancedSettings } from "@/services/settings/enhancedSettings" -import { PopupOption } from "@/services/option/defaultSettings" import * as PageActionBackground from "@/services/pageAction/background" import { BgData } from "@/services/backgroundData" import { ContextMenu } from "@/services/contextMenus" import { closeWindow, windowExists, getCurrentTab } from "@/services/chrome" import { WindowStackManager } from "@/services/windowStackManager" import { PopupAutoClose } from "@/services/popupAutoClose" -import { - isSearchCommand, - isPageActionCommand, - isAiPromptCommand, - findMatchingPageRule, -} from "@/lib/utils" +import { findMatchingPageRule } from "@/lib/utils" import { execute } from "@/action/background" import * as ActionHelper from "@/action/helper" import type { WindowType } from "@/types" import { Storage, SESSION_STORAGE_KEY } from "@/services/storage" -import { - ANALYTICS_EVENTS, - sendEvent, - getOrCreateClientId, -} from "@/services/analytics" +import { ANALYTICS_EVENTS, sendEvent } from "@/services/analytics" import * as HubBackground from "@/services/hub/background" import { importIf } from "@import-if" @@ -46,10 +35,6 @@ export type addPageRuleProps = { url: string } -type addCommandProps = { - command: string -} - const getTabId = ( _: unknown, sender: Sender, @@ -159,117 +144,6 @@ const commandFuncs = { return false }, - [BgCommand.addCommand]: ( - param: addCommandProps, - _: Sender, - response: (res: unknown) => void, - ): boolean => { - const command = JSON.parse(param.command) - const isSearch = isSearchCommand(command) - const isPageAction = isPageActionCommand(command) - const isAiPrompt = isAiPromptCommand(command) - const sourceType = (command as { sourceType?: unknown }).sourceType - const sourceId = (command as { sourceId?: unknown }).sourceId - const normalizedSourceType = Object.values(COMMAND_SOURCE_TYPE).includes( - sourceType as COMMAND_SOURCE_TYPE, - ) - ? (sourceType as COMMAND_SOURCE_TYPE) - : undefined - const sourceInfo = { - sourceType: normalizedSourceType, - sourceId: typeof sourceId === "string" ? sourceId : undefined, - } - - const cmd = isSearch - ? { - id: command.id, - title: command.title, - searchUrl: command.searchUrl, - iconUrl: command.iconUrl, - ...sourceInfo, - openMode: command.openMode, - openModeSecondary: command.openModeSecondary, - spaceEncoding: command.spaceEncoding, - popupOption: PopupOption, - } - : isAiPrompt - ? { - id: command.id, - title: command.title, - iconUrl: command.iconUrl, - ...sourceInfo, - openMode: command.openMode, - aiPromptOption: command.aiPromptOption, - popupOption: PopupOption, - } - : isPageAction - ? { - id: command.id, - title: command.title, - iconUrl: command.iconUrl, - ...sourceInfo, - openMode: command.openMode, - pageActionOption: command.pageActionOption, - popupOption: PopupOption, - } - : null - - if (!cmd) { - console.error("invalid command", param.command) - response({ result: false, error: "Invalid command format" }) - return true - } - - Settings.addCommands([cmd]) - .then(() => { - return sendEvent( - ANALYTICS_EVENTS.COMMAND_ADD, - { - event_label: cmd.openMode, - source_type: sourceInfo.sourceType, - source_id: sourceInfo.sourceId, - }, - SCREEN.COMMAND_HUB, - ) - }) - .then(async () => { - const clientId = await getOrCreateClientId() - response({ result: true, install_id: clientId }) - }) - .catch((err) => { - console.error("[addCommand] Failed:", err) - response({ result: false, error: err?.message ?? "Unknown error" }) - }) - return true - }, - - [BgCommand.removeCommand]: ( - param: { id: string }, - _: Sender, - response: (res: unknown) => void, - ): boolean => { - const remove = async () => { - const current = await Storage.getCommands() - const commandToRemove = current.find((c) => c.id === param.id) - if (!commandToRemove) { - response({ result: false, error: "Command not found" }) - return - } - const newCommands = current.filter((c) => c.id !== param.id) - await Storage.setCommands(newCommands) - await sendEvent( - ANALYTICS_EVENTS.COMMAND_REMOVE, - { - event_label: commandToRemove.openMode, - }, - SCREEN.COMMAND_HUB, - ) - response({ result: true }) - } - remove() - return true - }, - [BgCommand.canOpenInTab]: ( _: unknown, sender: Sender, @@ -414,6 +288,8 @@ const commandFuncs = { // [BgCommand.shareCommandToHub]: HubBackground.shareCommandToHub, [BgCommand.editCommandToHub]: HubBackground.editCommandToHub, + [BgCommand.pushEditToHub]: HubBackground.pushEditToHub, + [BgCommand.getSharedCommandIds]: HubBackground.getSharedCommandIds, // // PageAction diff --git a/packages/extension/src/components/option/ShareButton.tsx b/packages/extension/src/components/option/ShareButton.tsx index 3694c9e0c..54d201e78 100644 --- a/packages/extension/src/components/option/ShareButton.tsx +++ b/packages/extension/src/components/option/ShareButton.tsx @@ -4,27 +4,46 @@ import { Tooltip } from "@/components/Tooltip" import { cn, isUUIDv7, generateId } from "@/lib/utils" import { t } from "@/services/i18n" import { shareCommandToHub } from "@/services/hubShare" -import { NEW_HUB_SHAREABLE_OPEN_MODES, COMMAND_SOURCE_TYPE } from "@/const" +import { + NEW_HUB_SHAREABLE_OPEN_MODES, + COMMAND_SOURCE_TYPE, + NEW_HUB_URL, +} from "@/const" +import { getHubLocale } from "@/services/hubShare" import type { SelectionCommand } from "@/types" const VALID_SOURCE_TYPES = new Set([ COMMAND_SOURCE_TYPE.SELF_CREATED, COMMAND_SOURCE_TYPE.SELF_UPDATED, + COMMAND_SOURCE_TYPE.SELF_REINSTALL, COMMAND_SOURCE_TYPE.UNKNOWN, ]) type Props = { command: SelectionCommand onCommandIdChange?: (newId: string) => void + isShared?: boolean } -export const ShareButton = ({ command, onCommandIdChange }: Props) => { +export const ShareButton = ({ + command, + onCommandIdChange, + isShared, +}: Props) => { const buttonRef = useRef(null) const [status, setStatus] = useState<"idle" | "sent" | "error">("idle") const handleClick = (e: React.MouseEvent) => { e.stopPropagation() + if (isShared) { + // Open the hub dashboard page for the shared command + const locale = getHubLocale() + const url = `${NEW_HUB_URL}/${locale}/dashboard/commands?id=${encodeURIComponent(command.id)}` + chrome.tabs.create({ url }) + return + } + let commandToShare = command if (!isUUIDv7(command.id)) { const newId = generateId() @@ -52,6 +71,7 @@ export const ShareButton = ({ command, onCommandIdChange }: Props) => { className={cn( "outline-gray-200 p-2 rounded-md transition hover:bg-green-100 hover:scale-125 group/share-btn", "disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none", + isShared && "bg-green-50", )} onClick={handleClick} ref={buttonRef} @@ -59,6 +79,7 @@ export const ShareButton = ({ command, onCommandIdChange }: Props) => { { ) diff --git a/packages/extension/src/components/option/editor/CommandEditDialog.tsx b/packages/extension/src/components/option/editor/CommandEditDialog.tsx index c9a4601b7..61e4395b0 100644 --- a/packages/extension/src/components/option/editor/CommandEditDialog.tsx +++ b/packages/extension/src/components/option/editor/CommandEditDialog.tsx @@ -82,6 +82,7 @@ import { Ipc, BgCommand } from "@/services/ipc" import { getScreenSize } from "@/services/screen" import { Storage, SESSION_STORAGE_KEY } from "@/services/storage" import { ANALYTICS_EVENTS, sendEvent } from "@/services/analytics" +import { pushEditToHub } from "@/services/hubShare" import { isEmpty, e2a, cn, parseGeminiUrl, generateId } from "@/lib/utils" import { t as _t } from "@/services/i18n" @@ -118,13 +119,13 @@ const getDefault = ( ) => { const sourceDefaults = isEmpty(base?.id) ? { - sourceType: base?.sourceType ?? COMMAND_SOURCE_TYPE.SELF_CREATED, - sourceId: base?.sourceId ?? COMMAND_SOURCE_ID.SELF_CREATED, - } + sourceType: base?.sourceType ?? COMMAND_SOURCE_TYPE.SELF_CREATED, + sourceId: base?.sourceId ?? COMMAND_SOURCE_ID.SELF_CREATED, + } : { - sourceType: base?.sourceType, - sourceId: base?.sourceId, - } + sourceType: base?.sourceType, + sourceId: base?.sourceId, + } if (isSearchOpenMode(openMode)) { if (isSearchOpenMode(preOpenMode)) { @@ -274,6 +275,7 @@ type CommandEditDialogProps = { initialCommand?: SelectionCommand selectedType: COMMAND_TYPE onTypeClick: () => void + isShared?: boolean } export const CommandEditDialog = (param: CommandEditDialogProps) => { @@ -293,6 +295,7 @@ const CommandEditDialogInner = ({ initialCommand, selectedType, onTypeClick, + isShared, }: CommandEditDialogProps) => { const [initialized, setInitialized] = useState(false) const [assistDialogOpen, setAssistDialogOpen] = useState(false) @@ -327,6 +330,28 @@ const CommandEditDialogInner = ({ // Determine if open mode selection should be shown const shouldShowOpenModeSelect = selectedType === COMMAND_TYPE.SEARCH + // Process form data before submit: apply coercions and clean up unused fields. + const processFormData = (data: CommandSchemaType): CommandSchemaType => { + const d = { ...data } + if (isEmpty(d.id)) d.id = generateId() + if (d.revision == null) d.revision = 0 + if (d.parentFolderId === ROOT_FOLDER) { + d.parentFolderId = undefined + } + if (isSearchType(d)) { + if (d.popupOption != null) { + d.popupOption = { + width: Number(d.popupOption.width), + height: Number(d.popupOption.height), + } + } + if (d.openMode !== OPEN_MODE.WINDOW) { + delete d.windowState + } + } + return d + } + const variableArray = useFieldArray({ name: "variables", control: form.control, @@ -877,28 +902,34 @@ const CommandEditDialogInner = ({ {t("labelCancel")} + {isShared && !isHubEdit && ( + + )}