diff --git a/.gitignore b/.gitignore index 9cc8373..488eb2b 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,5 @@ coverage /test-results/ /playwright-report/ /playwright/.cache/ + +**/vite.config.ts.timestamp-* diff --git a/manifest.config.ts b/manifest.config.ts index e2c09ac..c6e158b 100644 --- a/manifest.config.ts +++ b/manifest.config.ts @@ -6,7 +6,7 @@ const [major, minor, patch, label = '0'] = packageJson.version.replace(/[^\d.-]+ const isDev = process.env.NODE_ENV === 'development' -export default defineManifest(async env => ({ +export default defineManifest(async () => ({ manifest_version: 3, name: isDev ? '[DEV] Gachon Tools - 사이버캠퍼스 확장프로그램' : 'Gachon Tools - 사이버캠퍼스 확장프로그램', description: packageJson.description, diff --git a/src/components/ActivityItem.tsx b/src/components/ActivityItem.tsx deleted file mode 100644 index 8197ba1..0000000 --- a/src/components/ActivityItem.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { Badge, Highlight, Box, Card, CardBody, Flex, Text, useColorModeValue } from '@chakra-ui/react' -import { format, formatDistanceToNowStrict, isValid } from 'date-fns' -import { ko } from 'date-fns/locale' - -import { CheckIcon, XMarkIcon } from './Icons' -import type { ActivityType } from '@/types' - -type Props = { - activity: ActivityType -} - -const ActivityItem = ({ activity }: Props) => { - const endAtDate = new Date(activity.endAt) - const dDay = isValid(endAtDate) ? formatDistanceToNowStrict(endAtDate, { addSuffix: true, locale: ko }) : '기한 없음' - - const cardBgHover = useColorModeValue('gray.50', 'gray.800') - const descriptionColor = useColorModeValue('gray.500', 'gray.400') - - return ( - - - - {activity.hasSubmitted ? : } - - - - - {activity.title} - - - - {activity.courseTitle} - - - - - - - {`${dDay} 마감`} - - - - {isValid(endAtDate) && `~${format(endAtDate, 'yyyy.MM.dd HH:mm')}`} - - - - - ) -} - -export default ActivityItem - -const ActivityBadge = ({ type }: { type: ActivityType['type'] }) => { - const badge = { - assignment: { color: 'blue', text: '과제' }, - video: { color: 'gray', text: '영상' }, - }[type] - - return ( - - {badge.text} - - ) -} diff --git a/src/components/ActivityList.tsx b/src/components/ActivityList.tsx deleted file mode 100644 index 6b83f93..0000000 --- a/src/components/ActivityList.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { format, formatDistanceToNowStrict, isValid } from 'date-fns' -import { ko } from 'date-fns/locale' -import { motion } from 'framer-motion' - -import { CheckIcon, XMarkIcon } from './Icons' -import type { ActivityType } from '@/types' - -type ActivityListProps = { - contentData: ActivityType[] -} - -export const ActivityList = ({ contentData }: ActivityListProps) => { - return ( - - {contentData.length > 0 ? ( - contentData.map(activity => ) - ) : ( -

진행중인 과제가 없습니다.

- )} -
- ) -} - -type ActivityItemProps = { - activity: ActivityType -} - -const ActivityItem = ({ activity }: ActivityItemProps) => { - const endAtDate = new Date(activity.endAt) - const dDay = isValid(endAtDate) ? formatDistanceToNowStrict(endAtDate, { addSuffix: true, locale: ko }) : '기한 없음' - - return ( - -
-
- {activity.hasSubmitted ? : } -
-
- -

{activity.title}

-
-

{activity.courseTitle}

-
-
-
-

- 마감 {dDay} -

-

- {isValid(endAtDate) && `~${format(endAtDate, 'yyyy.MM.dd HH:mm')}`} -

-
-
-
- ) -} - -const ActivityBadge = ({ type }: { type: ActivityType['type'] }) => { - const badge = { - assignment: { color: 'bg-blue-500', text: '과제' }, - video: { color: 'bg-gray-500', text: '영상' }, - }[type] - - return {badge.text} -} - -export default ActivityList diff --git a/src/components/ChakraMotion.tsx b/src/components/ChakraMotion.tsx deleted file mode 100644 index e635d1d..0000000 --- a/src/components/ChakraMotion.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { chakra, shouldForwardProp, forwardRef, type ChakraProps } from '@chakra-ui/react' -import { isValidMotionProp, motion, type MotionProps } from 'framer-motion' - -import type { ComponentPropsWithoutRef } from 'react' - -const ChakraMotion = ( - { as, children, ...props }: ComponentPropsWithoutRef & { as?: C } & MotionProps & Omit, - ref: React.ComponentPropsWithRef['ref'], -) => { - const Component = as || 'div' - - const MotionComponent = chakra(motion(Component), { - shouldForwardProp: prop => isValidMotionProp(prop) || shouldForwardProp(prop), - }) - - return ( - - {children} - - ) -} - -export default forwardRef(ChakraMotion) diff --git a/src/components/ContentModal.tsx b/src/components/ContentModal.tsx deleted file mode 100644 index 4b45ac3..0000000 --- a/src/components/ContentModal.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import { formatDistanceToNowStrict, isValid } from 'date-fns' -import { ko } from 'date-fns/locale' -import { AnimatePresence, motion } from 'framer-motion' -import { useState, useMemo } from 'react' - -import CourseList from './CourseList' -import { RefreshIcon, SettingIcon } from './Icons' -import PopoverOptions from './PopoverOptions' -import TabContent from './TabContent' -import useGetContents from '@/hooks/useGetContents' - -type Props = { - isOpen: boolean - onClose: () => void -} - -const ContentModal = ({ isOpen, onClose }: Props) => { - const [selectedCourseId, setSelectedCourseId] = useState('-1') - const { - data: { courseList, activityList, updateAt }, - pos, - refetch, - isLoading, - } = useGetContents({ enabled: isOpen }) - - const loadingText = useMemo(() => { - if (isLoading) { - return '불러오는 중...' - } - - const updateAtDate = new Date(updateAt) - if (!isValid(updateAtDate)) { - return '업데이트 날짜 없음' - } - - const time = formatDistanceToNowStrict(updateAtDate, { addSuffix: true, locale: ko }) - return `${time} 업데이트` - }, [isLoading, updateAt]) - - return ( - - {isOpen && ( -
- - -
-

Gachon Tools

- -
- -
-
- -
-
-
- -
-
- -
- - - 설정 - - } - /> -
-
- {loadingText} - -
-
- -
- )} - - ) -} - -export default ContentModal diff --git a/src/components/CourseList.tsx b/src/components/CourseList.tsx deleted file mode 100644 index 3c9dfb6..0000000 --- a/src/components/CourseList.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import type { Course } from '@/types' - -type Props = { - courseList: Course[] - selectedCourseId: string - setSelectedCourseId: React.Dispatch> -} - -const CourseList = ({ courseList, selectedCourseId, setSelectedCourseId }: Props) => { - const isSelected = (id: string) => selectedCourseId === id - - return ( -
- {courseList.map(course => ( - - ))} -
- ) -} - -export default CourseList diff --git a/src/components/ItemList.tsx b/src/components/ItemList.tsx deleted file mode 100644 index 0e70513..0000000 --- a/src/components/ItemList.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { Divider, Stack } from '@chakra-ui/react' -import { Fragment } from 'react' - -import type { ComponentProps } from 'react' - -interface ItemListProps extends Omit, 'children'> { - data: T[] - renderItem: (data: T) => JSX.Element | boolean | null | undefined - renderEmpty?: () => JSX.Element | boolean | null | undefined - hasDivider?: boolean -} - -export default function ItemList({ data, renderItem, renderEmpty, hasDivider = false, ...props }: ItemListProps) { - return ( - - {data?.length === 0 - ? renderEmpty?.() - : data.map((item, index) => { - return ( - - {renderItem(item)} - {renderItem(item) && hasDivider && index !== data.length - 1 && } - - ) - })} - - ) -} diff --git a/src/components/LoadingProgress.tsx b/src/components/LoadingProgress.tsx deleted file mode 100644 index 4e99d06..0000000 --- a/src/components/LoadingProgress.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { motion } from 'framer-motion' - -type Props = { - pos: number -} - -const LoadingProgress = ({ pos }: Props) => { - const circumference = 2 * Math.PI * 28 // 2πr, where r = 28 (the radius of our circle) - const strokeDashoffset = circumference - (pos / 100) * circumference - - return ( -
- - - - - -
- {Math.round(pos)}% -
-
-
- ) -} - -export default LoadingProgress diff --git a/src/components/Popover.tsx b/src/components/Popover.tsx deleted file mode 100644 index 04348b3..0000000 --- a/src/components/Popover.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import React, { useState, useEffect, useRef, ReactNode } from 'react' - -type PopoverProps = { - trigger: ReactNode - content: ReactNode - contentClassName?: string -} - -const Popover: React.FC = ({ trigger, content, contentClassName }) => { - const [isOpen, setIsOpen] = useState(false) - const popoverRef = useRef(null) - const triggerRef = useRef(null) - - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if ( - popoverRef.current && - !popoverRef.current.contains(event.target as Node) && - triggerRef.current && - !triggerRef.current.contains(event.target as Node) - ) { - setIsOpen(false) - } - } - - const handleEscapeKey = (event: KeyboardEvent) => { - if (event.key === 'Escape') { - setIsOpen(false) - } - } - - document.addEventListener('mousedown', handleClickOutside) - document.addEventListener('keydown', handleEscapeKey) - - return () => { - document.removeEventListener('mousedown', handleClickOutside) - document.removeEventListener('keydown', handleEscapeKey) - } - }, []) - - return ( -
-
setIsOpen(!isOpen)} - onKeyDown={e => { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault() - setIsOpen(!isOpen) - } - }} - tabIndex={0} - role="button" - aria-haspopup="true" - aria-expanded={isOpen} - > - {trigger} -
- - {isOpen && ( -
- {content} -
- )} -
- ) -} - -export default Popover diff --git a/src/components/PopoverOptions.tsx b/src/components/PopoverOptions.tsx deleted file mode 100644 index 0259f37..0000000 --- a/src/components/PopoverOptions.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import React, { useState } from 'react' - -import Popover from './Popover' -import packageJson from '../../package.json' - -const { version } = packageJson - -type Props = { - triggerElement: React.ReactNode -} - -const PopoverOptions: React.FC = ({ triggerElement }) => { - const [colorMode, setColorMode] = useState<'light' | 'dark'>(() => { - if (typeof window !== 'undefined') { - return (window.localStorage.getItem('color-theme') as 'light' | 'dark') || 'light' - } - return 'light' - }) - - const toggleColorMode = (mode: 'light' | 'dark') => { - setColorMode(mode) - if (typeof window !== 'undefined') { - window.localStorage.setItem('color-theme', mode) - if (mode === 'dark') { - document.documentElement.classList.add('dark') - } else { - document.documentElement.classList.remove('dark') - } - } - } - - const popoverContent = ( - <> -

설정

-
-
- 테마 : - - -
-
- 단축키 : - - - {navigator.platform.includes('Mac') ? '⌘' : 'Ctrl'} - - {' + '} - - / - - -
-
-
- ver.{version} -
- - ) - - return -} - -export default PopoverOptions diff --git a/src/components/ShadowChakraProvider.tsx b/src/components/ShadowChakraProvider.tsx deleted file mode 100644 index 49f00eb..0000000 --- a/src/components/ShadowChakraProvider.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { ChakraProvider } from '@chakra-ui/react' -import { createContext, useContext, useRef } from 'react' - -import { ShadowRootWrapper } from './ShadowRootWrapper' -import { customTheme } from '@/constants/customTheme' - -type Props = { - children: React.ReactNode -} - -export function ShadowChakraProvider({ children }: Props) { - const rootRef = useRef(null) - - return ( - // - -
- {children} -
-
- //
- ) -} - -const RootRefContext = createContext>(null) - -export const useRootRefContext = () => { - return useContext(RootRefContext) -} diff --git a/src/components/ShadowRootWrapper.tsx b/src/components/ShadowRootWrapper.tsx deleted file mode 100644 index 2d338de..0000000 --- a/src/components/ShadowRootWrapper.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import createCache from '@emotion/cache' -import { CacheProvider, EmotionCache } from '@emotion/react' -import { useEffect, useRef, useState } from 'react' - -type Props = { - children: React.ReactNode -} - -export function ShadowRootWrapper({ children }: Props) { - const shadowRef = useRef(null) - - const [emotionCache, setEmotionCache] = useState(null) - - useEffect(() => { - if (!shadowRef.current?.shadowRoot) return - - const cache = createCache({ - key: 'shadow', - container: shadowRef.current.shadowRoot, - }) - - setEmotionCache(cache) - }, []) - - return {emotionCache ? children : null} -} diff --git a/src/components/TabContent.tsx b/src/components/TabContent.tsx deleted file mode 100644 index ae6634a..0000000 --- a/src/components/TabContent.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { AnimatePresence, motion } from 'framer-motion' -import { useState } from 'react' - -import ActivityList from './ActivityList' -import LoadingProgress from './LoadingProgress' -import { TAB_LIST } from '@/constants' -import useFilteredActivityList from '@/hooks/useFilteredActivityList' -import type { ActivityType } from '@/types' - -type Props = { - activityList: ActivityType[] - selectedCourseId: string - pos: number - isLoading: boolean -} - -const TabContent = ({ activityList, selectedCourseId, pos, isLoading }: Props) => { - const [tabIndex, setTabIndex] = useState(0) - const filteredActivities = useFilteredActivityList(activityList, selectedCourseId, tabIndex, false) - - return ( -
-
- {TAB_LIST.map((tab, index) => ( - - ))} -
- - - - {isLoading ? : } - - -
- ) -} - -export default TabContent diff --git a/src/constants/customTheme.ts b/src/constants/customTheme.ts deleted file mode 100644 index 7f8e5bc..0000000 --- a/src/constants/customTheme.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { ThemeConfig, extendTheme, withDefaultColorScheme } from '@chakra-ui/react' -import { mode, type StyleFunctionProps } from '@chakra-ui/theme-tools' - -const config: ThemeConfig = { - initialColorMode: 'light', - useSystemColorMode: true, -} - -export const customTheme = extendTheme( - { - config, - components: { - Text: { - baseStyle: (props: StyleFunctionProps) => ({ - color: mode('gray.700', 'gray.200')(props), - margin: 0, - padding: 0, - }), - }, - }, - semanticTokens: { - colors: { - primary: { - default: '#2F6EA2', - }, - modalBg: { - default: 'white', - _dark: 'gray.700', - }, - }, - }, - }, - withDefaultColorScheme({ colorScheme: 'gray' }), -) diff --git a/src/constants/index.ts b/src/constants/index.ts index 213afe7..92473a3 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -1,3 +1 @@ -export const REFRESH_TIME = 1000 * 60 * 10 // 10분 - export const TAB_LIST = ['진행중인 과제', '모든 과제'] diff --git a/src/content/components/ContentWrapper.tsx b/src/content/components/ContentWrapper.tsx index 8f9eeaf..c33bee8 100644 --- a/src/content/components/ContentWrapper.tsx +++ b/src/content/components/ContentWrapper.tsx @@ -6,7 +6,7 @@ export function ContentWrapper({ children }: PropsWithChildren) { const { theme } = useThemeContext() return ( -
+
{children}
) diff --git a/src/content/components/theme-context.ts b/src/content/components/theme-context.ts index 416d399..696c278 100644 --- a/src/content/components/theme-context.ts +++ b/src/content/components/theme-context.ts @@ -1,6 +1,7 @@ -import { Theme } from 'daisyui' import { createContext, useContext } from 'react' +import type { Theme } from 'daisyui' + type ThemeContextValue = { theme: T setTheme: (theme: T) => void diff --git a/src/popup/Popup.tsx b/src/popup/Popup.tsx index 4f38856..718cb92 100644 --- a/src/popup/Popup.tsx +++ b/src/popup/Popup.tsx @@ -1,38 +1,45 @@ -import { Center, Flex, Link, Text, Tooltip } from '@chakra-ui/react' - -import { FeedbackIcon, GachonLogoIcon, GithubIcon, NotionIcon } from '@/components/Icons' +import { GachonLogoIcon, GithubIcon, FeedbackIcon, NotionIcon } from '@/components/Icons' function App() { return ( -
- - - Gachon Tools - - 가천대학교 사이버캠퍼스 확장 프로그램 +
+ +

Gachon Tools

+

가천대학교 사이버캠퍼스 확장 프로그램

- - - - - - - - - - - - - +
+ + + +
+
+ + + +
+
+ + +
+
+
) } diff --git a/src/popup/index.html b/src/popup/index.html index 61c14b2..53ac992 100644 --- a/src/popup/index.html +++ b/src/popup/index.html @@ -5,7 +5,7 @@ Gachon Tools -
+
diff --git a/src/popup/index.tsx b/src/popup/index.tsx index 4e65378..1654f69 100644 --- a/src/popup/index.tsx +++ b/src/popup/index.tsx @@ -1,7 +1,15 @@ import Popup from './Popup' +import { ContentWrapper } from '@/content/components/ContentWrapper' +import { ThemeContext } from '@/content/components/theme-context' import styles from '@/styles/index.css?inline' import createShadowRoot from '@/utils/createShadowRoot' const root = createShadowRoot(styles) -root.render() +root.render( + {} }}> + + + + , +) diff --git a/src/styles/index.css b/src/styles/index.css index 5ec8344..358dc96 100644 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -6,5 +6,6 @@ all: initial; font-weight: 500 !important; font-size: 16px !important; + line-height: 1.5 !important; letter-spacing: normal; } diff --git a/src/utils/createShadowRoot.tsx b/src/utils/createShadowRoot.tsx index cb843e5..c2ff887 100644 --- a/src/utils/createShadowRoot.tsx +++ b/src/utils/createShadowRoot.tsx @@ -1,7 +1,7 @@ import { createRoot } from 'react-dom/client' -export default function createShadowRoot(styles: string) { - const root = document.createElement('div') +export default function createShadowRoot(styles: string, options: { root?: HTMLElement } = {}) { + const root = options.root || document.createElement('div') const shadow = root.attachShadow({ mode: 'open' }) const globalStyleSheet = new CSSStyleSheet() diff --git a/src/utils/mapElement.ts b/src/utils/mapElement.ts index edcbe26..f8aa037 100644 --- a/src/utils/mapElement.ts +++ b/src/utils/mapElement.ts @@ -1,4 +1,4 @@ -import * as cheerio from 'cheerio' +import type * as cheerio from 'cheerio' export function mapElement( element: cheerio.Cheerio, diff --git a/tests/e2e/fixtures.ts b/tests/e2e/fixtures.ts index 07c9312..682fe4c 100644 --- a/tests/e2e/fixtures.ts +++ b/tests/e2e/fixtures.ts @@ -9,11 +9,7 @@ const test = base.extend<{ const pathToExtension = path.join(path.resolve(), './dist') const context = await chromium.launchPersistentContext('', { headless: false, - args: [ - `--headless=new`, - `--disable-extensions-except=${pathToExtension}`, - `--load-extension=${pathToExtension}`, - ], + args: [`--headless=new`, `--disable-extensions-except=${pathToExtension}`, `--load-extension=${pathToExtension}`], }) await use(context) await context.close() diff --git a/vite.config.ts b/vite.config.ts index 5b253e3..bf9ce68 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,11 +2,13 @@ import { crx } from '@crxjs/vite-plugin' import react from '@vitejs/plugin-react-swc' import { resolve } from 'path' -import { defineConfig, Plugin } from 'vite' +import { defineConfig } from 'vite' import tsconfigPaths from 'vite-tsconfig-paths' import manifest from './manifest.config' +import type { Plugin } from 'vite' + const viteManifestHackIssue846: Plugin & { renderCrxManifest: (manifest: any, bundle: any) => void } = {