diff --git a/chrome-extension/lib/background/index.ts b/chrome-extension/lib/background/index.ts index 2eb1509f..cd332682 100644 --- a/chrome-extension/lib/background/index.ts +++ b/chrome-extension/lib/background/index.ts @@ -11,6 +11,7 @@ import { I18n, requestObserverMemoPage, requestUpdateSidePanel, + responseGetExtensionManifest, responseOpenSidePanel, Storage, Tab, @@ -100,3 +101,5 @@ chrome.tabs.onUpdated.addListener(async () => { // content-ui에서 메시지를 전달받아 사이드 패널을 연다. responseOpenSidePanel(); + +responseGetExtensionManifest(); diff --git a/packages/shared/src/utils/extension/bridge/getExtensionManifest.ts b/packages/shared/src/utils/extension/bridge/getExtensionManifest.ts new file mode 100644 index 00000000..780c48a2 --- /dev/null +++ b/packages/shared/src/utils/extension/bridge/getExtensionManifest.ts @@ -0,0 +1,11 @@ +import { EXTENSION_ID } from '@src/constants'; +import { Runtime } from '../module'; + +export const BRIDGE_TYPE_GET_EXTENSION_MANIFEST = 'BRIDGE_TYPE_GET_EXTENSION_MANIFEST'; +export const requestGetExtensionManifest = (callbackFn: (response: chrome.runtime.Manifest) => void) => { + chrome.runtime.sendMessage(EXTENSION_ID, { type: BRIDGE_TYPE_GET_EXTENSION_MANIFEST }, callbackFn); +}; +export const responseGetExtensionManifest = () => { + const manifest = chrome.runtime.getManifest(); + Runtime.onMessageExternal(BRIDGE_TYPE_GET_EXTENSION_MANIFEST, (_, __, sendResponse) => sendResponse(manifest)); +}; diff --git a/packages/shared/src/utils/extension/bridge/getSidePanelOpen.ts b/packages/shared/src/utils/extension/bridge/getSidePanelOpen.ts new file mode 100644 index 00000000..74b539be --- /dev/null +++ b/packages/shared/src/utils/extension/bridge/getSidePanelOpen.ts @@ -0,0 +1,10 @@ +import { EXTENSION_ID } from '@src/constants'; +import { Runtime } from '../module'; + +export const BRIDGE_TYPE_GET_SIDE_PANEL_OPEN = 'BRIDGE_TYPE_GET_SIDE_PANEL_OPEN'; +export const requestGetSidePanelOpen = (callbackFn: () => void) => + chrome.runtime.sendMessage(EXTENSION_ID, { type: BRIDGE_TYPE_GET_SIDE_PANEL_OPEN }, callbackFn); +export const responseGetSidePanelOpen = () => + Runtime.onMessageExternal(BRIDGE_TYPE_GET_SIDE_PANEL_OPEN, (_, __, sendResponse) => { + sendResponse(); + }); diff --git a/packages/shared/src/utils/extension/bridge/index.ts b/packages/shared/src/utils/extension/bridge/index.ts index 91100b99..a83027bf 100644 --- a/packages/shared/src/utils/extension/bridge/index.ts +++ b/packages/shared/src/utils/extension/bridge/index.ts @@ -5,3 +5,5 @@ export * from './getSummary'; export * from './updateSidePanel'; export * from './observeMemoPage'; export * from './refetchTheMemoList'; +export * from './getExtensionManifest'; +export * from './getSidePanelOpen'; diff --git a/packages/shared/src/utils/extension/bridge/type.ts b/packages/shared/src/utils/extension/bridge/type.ts index 2b41b9e4..40b27f95 100644 --- a/packages/shared/src/utils/extension/bridge/type.ts +++ b/packages/shared/src/utils/extension/bridge/type.ts @@ -1,3 +1,5 @@ +import { BRIDGE_TYPE_GET_EXTENSION_MANIFEST } from './getExtensionManifest'; +import { BRIDGE_TYPE_GET_SIDE_PANEL_OPEN } from './getSidePanelOpen'; import { BRIDGE_TYPE_GET_SUMMARY } from './getSummary'; import { BRIDGE_TYPE_OBSERVER_MEMO_PAGE } from './observeMemoPage'; import { BRIDGE_TYPE_OPEN_SIDE_PANEL } from './openSidePanel'; @@ -11,7 +13,9 @@ export type BridgeType = | typeof BRIDGE_TYPE_GET_SUMMARY | typeof BRIDGE_TYPE_UPDATE_SIDE_PANEL | typeof BRIDGE_TYPE_OBSERVER_MEMO_PAGE - | typeof BRIDGE_TYPE_REFETCH_THE_MEMO_LIST; + | typeof BRIDGE_TYPE_REFETCH_THE_MEMO_LIST + | typeof BRIDGE_TYPE_GET_EXTENSION_MANIFEST + | typeof BRIDGE_TYPE_GET_SIDE_PANEL_OPEN; export interface BridgeRequest<T> { type: BridgeType; payload?: T; diff --git a/packages/shared/src/utils/extension/module/runtime.ts b/packages/shared/src/utils/extension/module/runtime.ts index b0e70fcf..86a914b3 100644 --- a/packages/shared/src/utils/extension/module/runtime.ts +++ b/packages/shared/src/utils/extension/module/runtime.ts @@ -43,15 +43,15 @@ export class Runtime { */ static async onMessageExternal<TPayload>( type: BridgeType, - callbackFn: ( + callbackFn?: ( request: BridgeRequest<TPayload>, sender: chrome.runtime.MessageSender, - sendResponse: (response?: string) => void, + sendResponse: (response?: unknown) => void, ) => void, ): Promise<void> { return chrome.runtime.onMessageExternal.addListener((request, sender, sendResponse) => { if (!request?.type || request.type !== type) return; - callbackFn(request, sender, sendResponse); + callbackFn?.(request, sender, sendResponse); }); } diff --git a/packages/web/src/app/login/components/ExtensionVersion.tsx b/packages/web/src/app/login/components/ExtensionVersion.tsx new file mode 100644 index 00000000..ae8b935c --- /dev/null +++ b/packages/web/src/app/login/components/ExtensionVersion.tsx @@ -0,0 +1,28 @@ +'use client'; + +import { URL_CHROME_STORE } from '@extension/shared/constants'; +import { useDidMount } from '@extension/shared/hooks'; +import { requestGetExtensionManifest } from '@extension/shared/utils/extension'; +import { useState } from 'react'; + +export default function ExtensionVersion() { + const [version, setVersion] = useState<null | string>(null); + + useDidMount(() => { + try { + requestGetExtensionManifest(manifest => { + setVersion(manifest.version); + }); + } catch (e) { + const answer = window.confirm( + '익스텐션을 설치하지 않으셨군요. 설치해야 메모 기능을 이용하실 수 있습니다. 설치하러 가시겠습니까?', + ); + if (!answer) return; + location.href = URL_CHROME_STORE; + } + }); + + if (version) + return <p className="w-full text-sm absolute right-2 bottom-2 text-gray-600 text-end">설치된 버전 : {version}</p>; + return; +} diff --git a/packages/web/src/app/login/components/LoginSection.tsx b/packages/web/src/app/login/components/LoginSection.tsx index 93e2056d..86e9d13d 100644 --- a/packages/web/src/app/login/components/LoginSection.tsx +++ b/packages/web/src/app/login/components/LoginSection.tsx @@ -1,5 +1,6 @@ import { signInWithOAuth } from '@src/utils/supabase.server'; import Image from 'next/image'; +import ExtensionVersion from './ExtensionVersion'; export default async function LoginSection() { return ( @@ -19,6 +20,7 @@ export default async function LoginSection() { 구글 로그인 </button> </form> + <ExtensionVersion /> </section> ); } diff --git a/packages/web/src/app/login/components/index.ts b/packages/web/src/app/login/components/index.ts index 5c2098fe..a3ca0b53 100644 --- a/packages/web/src/app/login/components/index.ts +++ b/packages/web/src/app/login/components/index.ts @@ -1,2 +1,3 @@ export { default as PersonalInformationInfo } from './PersonalInformationInfo'; export { default as LoginSection } from './LoginSection'; +export { default as ExtensionVersion } from './ExtensionVersion'; diff --git a/packages/web/src/app/memos/components/MemoRefresh.tsx b/packages/web/src/app/memos/components/MemoRefresh.tsx index 4d6a8eea..e982635d 100644 --- a/packages/web/src/app/memos/components/MemoRefresh.tsx +++ b/packages/web/src/app/memos/components/MemoRefresh.tsx @@ -5,11 +5,13 @@ import React from 'react'; import { toast } from 'react-toastify'; import { motion, SVGMotionProps } from 'framer-motion'; +import { driverObj } from '../utils'; interface MemoRefreshProps extends SVGMotionProps<SVGSVGElement> {} export default function MemoRefresh({ ...props }: MemoRefreshProps) { const queryClient = useQueryClient(); const handleClick = async () => { + driverObj.moveNext(); await queryClient.invalidateQueries({ queryKey: queryKeys.memoList() }); toast.success('새로고침이 완료되었습니다.'); }; diff --git a/packages/web/src/app/memos/utils/guide.ts b/packages/web/src/app/memos/utils/guide.ts index 3afbf962..4be55aab 100644 --- a/packages/web/src/app/memos/utils/guide.ts +++ b/packages/web/src/app/memos/utils/guide.ts @@ -1,21 +1,33 @@ import { driver } from 'driver.js'; import 'driver.js/dist/driver.css'; +import { requestGetSidePanelOpen } from '@extension/shared/utils/extension'; export const driverObj = driver({ showProgress: true, allowClose: false, popoverClass: 'driverjs-theme', + nextBtnText: '다음', + doneBtnText: '종료', + prevBtnText: '이전', steps: [ { popover: { title: '환영합니다 🎉', description: `메모를 한 번 해볼까요?\nOption + S를 눌러 사이드 패널을 열어보세요 !`, + onPopoverRender: () => { + setInterval(() => { + requestGetSidePanelOpen(() => { + if (driverObj.getActiveIndex() !== 0) return; + driverObj.moveNext(); + }); + }, 500); + }, }, }, { popover: { title: '메모 저장', - description: `메모를 입력하시고, Command + S를 눌러 저장해보세요.\n메모 테두리가 파랑색 테두리에서 기본 테두리로 돌아왔다면 저장에 성공한 거에요!\n또한, 3초마다 자동으로 저장되니 안심하세요 ☺️`, + description: `잘하셨어요 !\n이제 이 사이드 패널에서 메모를 기록하실 수 있답니다.\n메모를 입력하고, Command + S를 눌러 저장해보세요.\n메모 테두리가 파랑색 테두리에서 기본 테두리로 돌아왔다면 저장에 성공한 거에요!\n또한, 3초마다 자동으로 저장되니 안심하세요 ☺️`, }, }, { diff --git a/pages/side-panel/src/SidePanel.tsx b/pages/side-panel/src/SidePanel.tsx index 839d3bc8..6fdac883 100644 --- a/pages/side-panel/src/SidePanel.tsx +++ b/pages/side-panel/src/SidePanel.tsx @@ -12,8 +12,12 @@ import { SummaryHeader, SummaryProvider, } from './components'; +import { useDidMount } from '@extension/shared/hooks'; +import { responseGetSidePanelOpen } from '@extension/shared/utils/extension'; export default function SidePanel() { + useDidMount(responseGetSidePanelOpen); + return ( <QueryProvider> <OverlayProvider>