-
Notifications
You must be signed in to change notification settings - Fork 1
Feat(extension): 익스텐션 페이지 레이아웃 및 북마크 저장 #64
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 15 commits
3632099
acf449c
1593a89
93483a9
5976c0c
df1d6b7
c9a12fb
da85f3f
9203a31
2966daa
dc223a2
0f09c02
34cacb1
c9efabb
c8a0041
f84798d
8f3576c
6240c9b
991d984
87dcb1c
7349edf
31c1382
238bafa
2361d30
08a4df3
8297730
9937c01
889707f
1a5919b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,11 @@ | ||
| <!doctype html> | ||
| <html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="utf-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||
| <meta charset="UTF-8" /> | ||
| <title>PinBack Extension</title> | ||
| </head> | ||
| <body> | ||
| <div id="root"></div> | ||
| <!-- src 경로는 프로젝트 루트 기준으로 모듈 import --> | ||
| <script type="module" src="/src/popup.tsx"></script> | ||
| </body> | ||
| </html> |
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 치삐 귀여워요
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 너가 더. |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,10 +1,161 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import './App.css'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| InfoBox, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Button, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Textarea, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DateTime, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Switch, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| PopupContainer, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Dropdown, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } from '@pinback/design-system/ui'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import Logo from '@assets/logo.svg'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useState } from 'react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { usePageMeta } from './hooks/usePageMeta'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useSaveBookmark } from './hooks/useSaveBookmarks'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { validateDate, validateTime } from '@utils/ValidateData'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const App = () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [isRemindOn, setIsRemindOn] = useState(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [memo, setMemo] = useState(''); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [isPopupOpen, setIsPopupOpen] = useState(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [selected, setSelected] = useState<string | null>(null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 시간,날짜 검사 구간! | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [date, setDate] = useState('2025.10.10'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [time, setTime] = useState('19:00'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [dateError, setDateError] = useState(''); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [timeError, setTimeError] = useState(''); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleDateChange = (value: string) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setDate(value); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setDateError(validateDate(value)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleTimeChange = (value: string) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setTime(value); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setTimeError(validateTime(value)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 스위치 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleSwitchChange = (checked: boolean) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setIsRemindOn(checked); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { url, title, description, imgUrl } = usePageMeta(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { save } = useSaveBookmark(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleSave = async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| save({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| description, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| imgUrl, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| memo, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isRemindOn, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| selectedCategory: selected, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| date: isRemindOn ? date : null, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| time: isRemindOn ? time : null, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const saveData = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| description, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| imgUrl, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| memo, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isRemindOn, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| selectedCategory: selected, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| date: isRemindOn ? date : null, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| time: isRemindOn ? time : null, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| createdAt: new Date().toISOString(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log('저장 데이터:', saveData); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+46
to
+72
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 저장 전 유효성 게이트 + 불필요한 로그/중복 객체 제거 + await 누락
- const handleSave = async () => {
- save({
+ const handleSave = async () => {
+ if (isRemindOn) {
+ const dErr = validateDate(date);
+ const tErr = validateTime(time);
+ setDateError(dErr);
+ setTimeError(tErr);
+ if (dErr || tErr) return;
+ }
+ await save({
url,
title,
description,
imgUrl,
memo,
isRemindOn,
selectedCategory: selected,
date: isRemindOn ? date : null,
time: isRemindOn ? time : null,
});
- const saveData = {
- url,
- title,
- description,
- imgUrl,
- memo,
- isRemindOn,
- selectedCategory: selected,
- date: isRemindOn ? date : null,
- time: isRemindOn ? time : null,
- createdAt: new Date().toISOString(),
- };
- console.log('저장 데이터:', saveData);
};📝 Committable suggestion
Suggested change
🧰 Tools🪛 GitHub Check: lint[warning] 70-70: 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="App"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex h-[50rem] w-[26rem] items-center justify-center bg-blue-500 text-2xl text-white"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 자 핀백 앱잼 시작~오늘은 7월 7일임~ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="relative flex h-[56.8rem] w-[31.2rem] items-center justify-center bg-[#00000000] text-2xl text-white"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {isPopupOpen && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <PopupContainer | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 키테고리 입력창에 대해 에러 메세지는 어떻게 나타내나요?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. input에서 입력값 받는 부분, 하단에 에러메시지 띄우는 부분이 공컴 작업에서 누락이 되었었나봐요! 해당 기능 추가해두었습니다~! |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isOpen={isPopupOpen} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClose={() => setIsPopupOpen(false)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type="input" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title="카테고리 추가하기" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| left="취소" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| right="확인" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| placeholder="카테고리 제목을 입력해주세요" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onLeftClick={() => setIsPopupOpen(false)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onRightClick={() => setIsPopupOpen(false)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex flex-col justify-between gap-[1.6rem] rounded-[12px] bg-white px-[3.2rem] py-[2.4rem] text-black"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="mr-auto" onClick={() => setIsPopupOpen(true)}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <img src={Logo} alt="로고" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <InfoBox | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title={title || '제목 없음'} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| source={description || '웹페이지'} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| imgUrl={imgUrl} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <p className="caption1-sb mb-[0.4rem]">카테고리</p> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Dropdown | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| options={['옵션1', '옵션2']} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| selectedValue={selected} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onChange={(value) => setSelected(value)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| placeholder="선택해주세요" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onAddItem={() => setIsPopupOpen(true)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| addItemLabel="추가하기" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <p className="caption1-sb mb-[0.4rem]">메모</p> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Textarea | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| maxLength={100} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| placeholder="나중에 내가 꺼내줄 수 있게 살짝 적어줘!" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value={memo} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onChange={(e) => setMemo(e.target.value)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="mb-[0.4rem] flex items-center justify-between"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <p className="caption1-sb">리마인드</p> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Switch | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onCheckedChange={handleSwitchChange} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| checked={isRemindOn} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="mb-[0.4rem] flex items-center justify-between gap-[0.8rem]"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <DateTime | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type="date" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| state={ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| dateError ? 'error' : isRemindOn ? 'default' : 'disabled' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value={date} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onChange={handleDateChange} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <DateTime | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type="time" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| state={ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| timeError ? 'error' : isRemindOn ? 'default' : 'disabled' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value={time} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onChange={handleTimeChange} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+147
to
+164
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainDateTime(time) 자식 → onChange 미전파로 handleTimeChange가 호출되지 않을 가능성 디자인시스템 검증 스크립트(자식 컴포넌트의 onChange 호출 여부): 빠른 수정 제안(DateTime.tsx 내부, time 분기에서 부모 onChange 호출): // beforeInput insert/삭제/붙여넣기 각 분기에서 setInput(...) 뒤에 추가
- setInput(formatTime12(next));
+ const formatted = formatTime12(next);
+ setInput(formatted);
+ props.onChange?.(formatted);
...
- setInput(formatTime12(next));
+ const formatted = formatTime12(next);
+ setInput(formatted);
+ props.onChange?.(formatted);
...
- setInput(formatTime12(next));
+ const formatted = formatTime12(next);
+ setInput(formatted);
+ props.onChange?.(formatted);🏁 Script executed: #!/bin/bash
rg -n 'DateTime\.tsx' packages/design-system/src/components/dateTime -n
rg -nP 'onChange\(.*\)' packages/design-system/src/components/dateTime/DateTime.tsx -n
rg -nP 'type === .time.*\)' -n -C3 packages/design-system/src/components/dateTime/DateTime.tsxLength of output: 0 🏁 Script executed: #!/bin/bash
# DateTime 컴포넌트 파일 경로 찾기
FILE=$(find packages/design-system -type f -iname "DateTime.tsx" | head -n1)
if [ -z "$FILE" ]; then
echo "DateTime.tsx 파일을 찾을 수 없습니다."
exit 0
fi
echo "Inspecting: $FILE"
echo -e "\n--- props.onChange 호출 여부 ---"
rg -n 'props\.onChange' "$FILE" || echo "없음"
echo -e "\n--- time 타입 분기 코드 ---"
rg -nP "type\\s*[:=]\\s*['\"]time" -nC3 "$FILE" || echo "없음"Length of output: 587 🏁 Script executed: #!/bin/bash
# DateTime.tsx 길이 및 time 분기 확인
FILE=$(find packages/design-system -type f -iname "DateTime.tsx" | head -n1)
if [ -z "$FILE" ]; then
echo "DateTime.tsx 파일을 찾을 수 없습니다."
exit 1
fi
echo "파일 경로: $FILE"
echo "총 라인 수:"
wc -l "$FILE"
echo -e "\n--- time 타입 분기 주변 코드 (컨텍스트 5줄) ---"
rg -n -C5 "type\\s*===\\s*['\"]time['\"]" "$FILE" || echo "time 분기 없음"
echo -e "\n--- props.onChange 호출 위치 (컨텍스트 5줄) ---"
rg -n -C5 "props\\.onChange" "$FILE" || echo "props.onChange 호출 없음"Length of output: 790 🏁 Script executed: #!/bin/bash
# DateTime.tsx에서 formatTime 사용 및 onChange 호출 확인
FILE=$(find packages/design-system -type f -iname "DateTime.tsx" | head -n1)
if [ -z "$FILE" ]; then
echo "DateTime.tsx 파일을 찾을 수 없습니다."
exit 1
fi
echo "파일 경로: $FILE"
echo
echo "--- formatTime 사용 위치 (컨텍스트 3줄) ---"
rg -n -C3 "formatTime" "$FILE" || echo "formatTime 없음"
echo
echo "--- formatTime 분기에서 props.onChange 호출 여부 (컨텍스트 3줄) ---"
rg -n -C3 "formatTime.*props\.onChange" "$FILE" || echo "formatTime 이후 onChange 호출 없음"Length of output: 1646 DateTime(time) onChange 미호출로 handleTimeChange 미실행 @@ packages/design-system/src/components/dateTime/DateTime.tsx
- setInput(formatTime12(next));
+ const formatted = formatTime12(next);
+ setInput(formatted);
+ props.onChange?.(formatted);
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {/* 에러 메시지 출력 */} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {dateError && <p className="body3-r text-error">{dateError}</p>} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {timeError && <p className="body3-r text-error">{timeError}</p>} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Button size="medium" onClick={handleSave}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 저장 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 로고가 모든 페이지에서 대부분 사용된다고 생각이드는데 디자인시스템에 저장하고 하나를 재사용하는 방싣에 대해 어떻게 생각하시나요? |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,39 @@ | ||
| console.log("백그라운드 기능") | ||
| console.log('백그라운드 기능'); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 불필요한 콘솔 제거! |
||
| chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => { | ||
| if (message.type === 'FETCH_OG_META') { | ||
| fetch(message.url) | ||
| .then((res) => res.text()) | ||
| .then((html) => { | ||
| const parser = new DOMParser(); | ||
| const doc = parser.parseFromString(html, 'text/html'); | ||
|
|
||
|
Comment on lines
+7
to
+9
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. MV3 서비스 워커에서 DOMParser 사용 불가 → 오프스크린 문서나 컨텐트 스크립트로 이전 필요. 서비스 워커에는 DOM이 없어 DOMParser를 쓸 수 없습니다. 원하시면 오프스크린 문서 기반으로 리팩터를 제안드리겠습니다. 🤖 Prompt for AI Agents |
||
| const getMeta = (prop) => | ||
| doc | ||
| .querySelector(`meta[property="${prop}"]`) | ||
| ?.getAttribute('content') || ''; | ||
|
|
||
| const makeAbsoluteUrl = (base, img) => { | ||
| try { | ||
| return img ? new URL(img, base).href : ''; | ||
| } catch { | ||
| return img; | ||
| } | ||
| }; | ||
|
|
||
| const image = getMeta('og:image'); | ||
|
|
||
| sendResponse({ | ||
| title: getMeta('og:title'), | ||
| description: getMeta('og:description'), | ||
| siteName: getMeta('og:site_name'), | ||
| image: makeAbsoluteUrl(message.url, image), | ||
| url: getMeta('og:url') || message.url, | ||
| }); | ||
|
Comment on lines
+4
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chain페치 안정성: 타임아웃·스킴 검증 추가, 호스트 권한 점검 필요.
if (message.type === 'FETCH_OG_META') {
- fetch(message.url)
+ try {
+ if (!/^https?:\/\//i.test(message.url)) throw new Error('unsupported scheme');
+ } catch {
+ sendResponse(null);
+ return true;
+ }
+ const ac = new AbortController();
+ const to = setTimeout(() => ac.abort(), 8000);
+ fetch(message.url, { signal: ac.signal })
.then((res) => res.text())
.then((html) => {
+ clearTimeout(to);
const parser = new DOMParser();권한 확인 스크립트: 🏁 Script executed: #!/bin/bash
# manifest.json에서 권한 확인
rg -n --json '"(permissions|host_permissions)"' apps/extension/manifest.json | jq -r '.data.lines.text' || trueLength of output: 130 Fetch 안정성 강화 및 스킴 검증
|
||
| }) | ||
| .catch((err) => { | ||
| console.error('OG fetch 실패:', err); | ||
| sendResponse(null); | ||
| }); | ||
| return true; // async 응답 | ||
| } | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| console.log("컨텐츠 스크립트") | ||
| console.log('컨텐츠 스크립트 로드됨'); | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,55 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useEffect, useState } from 'react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { OgImageFetcher } from '@utils/OGFetch'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export interface PageMeta { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| description: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| imgUrl: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+4
to
+9
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chain프로퍼티 네이밍 정합성: imgUrl → imageUrl 권장 디자인 시스템/OG 응답과 용어 통일을 위해 export interface PageMeta {
url: string;
title: string;
description: string;
- imgUrl: string;
+ imageUrl: string;
}
@@
const [meta, setMeta] = useState<PageMeta>({
url: '',
title: '',
description: '',
- imgUrl: '',
+ imageUrl: '',
});
@@
- imgUrl: ogMeta?.image ?? '',
+ imageUrl: ogMeta?.image ?? '',검증 스크립트: Also applies to: 16-16, 45-46 🏁 Script executed: #!/bin/bash
# imgUrl 사용처 점검
rg -nP '\bimgUrl\b' -C2Length of output: 4946 imgUrl → imageUrl로 프로퍼티 이름 통일 및 일괄 리팩토링 필요
모든 사용처를 함께 업데이트하여 타입 에러 및 런타임 오류를 방지하세요. 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const usePageMeta = () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [meta, setMeta] = useState<PageMeta>({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url: '', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title: '', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| description: '', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| imgUrl: '', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| chrome.tabs.query({ active: true, currentWindow: true }, async (tabs) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const activeTab = tabs[0]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!activeTab?.url) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const currentUrl = activeTab.url; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| chrome.storage.local.set({ bookmarkedUrl: currentUrl }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const imageUrl = await OgImageFetcher({ url: currentUrl }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 개발중에는 잠시 주석처리 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // const isInternalChromePage = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // /^chrome:\/\//.test(currentUrl) || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // /^edge:\/\//.test(currentUrl) || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // /^about:/.test(currentUrl); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // // ❌ chrome-extension:// 은 내부 페이지로 취급하지 않음 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // if (isInternalChromePage || !imageUrl?.title) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // window.close(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const newMeta = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url: currentUrl, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title: imageUrl.title ?? '', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| description: imageUrl.description ?? '', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| imgUrl: imageUrl.image ?? '', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const imageUrl = await OgImageFetcher({ url: currentUrl }); | |
| // 개발중에는 잠시 주석처리 | |
| // const isInternalChromePage = | |
| // /^chrome:\/\//.test(currentUrl) || | |
| // /^edge:\/\//.test(currentUrl) || | |
| // /^about:/.test(currentUrl); | |
| // // ❌ chrome-extension:// 은 내부 페이지로 취급하지 않음 | |
| // if (isInternalChromePage || !imageUrl?.title) { | |
| // window.close(); | |
| // return; | |
| // } | |
| const newMeta = { | |
| url: currentUrl, | |
| title: imageUrl.title ?? '', | |
| description: imageUrl.description ?? '', | |
| imgUrl: imageUrl.image ?? '', | |
| }; | |
| let ogMeta: { title?: string; description?: string; image?: string } | undefined; | |
| try { | |
| ogMeta = await OgImageFetcher({ url: currentUrl }); | |
| } catch (e) { | |
| console.warn('OG 메타 수집 실패', e); | |
| } | |
| // 개발중에는 잠시 주석처리 | |
| // const isInternalChromePage = | |
| // /^chrome:\/\//.test(currentUrl) || | |
| // /^edge:\/\//.test(currentUrl) || | |
| // /^about:/.test(currentUrl); | |
| // // ❌ chrome-extension:// 은 내부 페이지로 취급하지 않음 | |
| // if (isInternalChromePage || !ogMeta?.title) { | |
| // window.close(); | |
| // return; | |
| // } | |
| const newMeta: PageMeta = { | |
| url: currentUrl, | |
| title: ogMeta?.title ?? activeTab.title ?? '', | |
| description: ogMeta?.description ?? '', | |
| imgUrl: ogMeta?.image ?? '', | |
| }; |
🤖 Prompt for AI Agents
In apps/extension/src/hooks/usePageMeta.ts around lines 28 to 46, OgImageFetcher
can return undefined for internal pages causing a runtime TypeError when
accessing properties; wrap the fetch in try/catch, rename the result to ogMeta
(or ogData) for clarity, and add a safety guard using optional chaining and
nullish coalescing (e.g. ogMeta?.title ?? '') before building newMeta; if ogMeta
is undefined (or missing required fields) handle it gracefully (close the window
or abort) instead of accessing properties directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
부분적으로 역할이 많은거 같아서 몇 개의 함수로 분리해도 좋을 것 같은데 어떻게 생각하시나요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오 피드백 감사합니다! 역할에 따라 분리해두었습니다
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| interface SaveBookmarkParams { | ||
| url: string; | ||
| title: string; | ||
| description: string; | ||
| imgUrl: string; | ||
| memo: string; | ||
| isRemindOn: boolean; | ||
| selectedCategory: string | null; | ||
| date: string | null; | ||
| time: string | null; | ||
| } | ||
|
|
||
| export const useSaveBookmark = () => { | ||
| const save = async (params: SaveBookmarkParams) => { | ||
| try { | ||
| const saveData = { | ||
| ...params, | ||
| createdAt: new Date().toISOString(), | ||
| }; | ||
|
|
||
| console.log('저장 데이터:', saveData); | ||
|
||
|
|
||
| const result = await new Promise<{ bookmarks?: any[] }>((resolve) => { | ||
| chrome.storage.local.get(['bookmarks'], (items) => resolve(items)); | ||
| }); | ||
|
|
||
| const bookmarks = result.bookmarks || []; | ||
| bookmarks.push(saveData); | ||
|
|
||
| await new Promise<void>((resolve) => { | ||
| chrome.storage.local.set({ bookmarks }, resolve); | ||
| }); | ||
|
|
||
| chrome.bookmarks.create( | ||
| { | ||
| parentId: '1', | ||
| title: params.title || params.url, | ||
| url: params.url, | ||
| }, | ||
| (newBookmark) => { | ||
| console.log('크롬 북마크바에 저장 완료:', newBookmark); | ||
| } | ||
| ); | ||
|
Comment on lines
+32
to
+41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 북마크바 parentId 하드코딩(‘1’)은 환경에 따라 깨집니다. 동적으로 해석하고 Promise 기반 API로 대체하세요. 폴더 ID는 고정이 아닙니다. - chrome.bookmarks.create(
- {
- parentId: '1',
- title: params.title || params.url,
- url: params.url,
- },
- (newBookmark) => {
- console.log('크롬 북마크바에 저장 완료:', newBookmark);
- }
- );
+ // 북마크바 ID 해석
+ const tree = await chrome.bookmarks.getTree();
+ const findBar = (nodes: chrome.bookmarks.BookmarkTreeNode[]): string | null => {
+ for (const n of nodes) {
+ // Chrome 134+ folderType 제공. 없으면 이름/루트 자식으로 폴백.
+ if ((n as any).folderType === 'bookmarks-bar') return n.id;
+ if (n.children?.length) {
+ const r = findBar(n.children);
+ if (r) return r;
+ }
+ }
+ return null;
+ };
+ const parentId = findBar(tree) ?? undefined; // undefined면 "기타 북마크"로 생성
+
+ const newBookmark = await chrome.bookmarks.create({
+ parentId,
+ title: params.title || params.url,
+ url: params.url,
+ });
+ if (process.env.NODE_ENV !== 'production') {
+ console.log('크롬 북마크 저장 완료:', newBookmark);
+ }
🧰 Tools🪛 GitHub Check: lint[warning] 41-41: 🤖 Prompt for AI Agents
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 요 부분은 확인 안해도 괜찮을까요?? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @constantly-dev 네, 현재 단계에서는 넘어가셔도 괜찮습니다! 하드코딩된
권장사항:
현재 PR의 주요 목표인 익스텐션 레이아웃과 북마크 저장 기본 기능이 우선이므로, 이 부분은 기술부채로 남겨두고 나중에 개선하셔도 충분합니다. 진행하시면 됩니다! 👍 🧠 Learnings used |
||
|
|
||
| window.close(); | ||
| } catch (error) { | ||
| console.error('저장 중 오류:', error); | ||
| } | ||
| }; | ||
|
|
||
| return { save }; | ||
| }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
TS 엔트리 → .js로 변경은 빌드 파이프라인과 충돌 위험(소스에 .js 미존재)
레포는 TS 소스(
src/background.ts,src/content.ts)를 사용 중인데 매니페스트가.js를 직접 가리키면 번들러(예: vite-plugin-web-extension)가 엔트리 추적을 못 해 빌드/핫리로드가 깨질 수 있습니다. 실제로 소스 트리에src/background.js/src/content.js는 없을 확률이 큽니다. TS 엔트리를 그대로 두고 번들러가 JS를 산출하도록 되돌리는 것을 권장합니다.적용 제안:
"background": { - "service_worker": "src/background.js", + "service_worker": "src/background.ts", "type": "module" }, @@ "matches": ["<all_urls>"], - "js": ["src/content.js"] + "js": ["src/content.ts"]검증 스크립트(소스에 .js가 없는지, TS가 존재하는지 확인):
Also applies to: 17-18
🏁 Script executed:
Length of output: 100
manifest.json에서 service_worker·content 경로를 .ts로 수정 필요
실제
apps/extension/src에background.js/content.js가 없고background.ts/content.ts만 존재하여(스크립트로 확인), 번들러가 엔트리를 찾지 못해 빌드·핫리로드가 깨집니다. 아래처럼.js→.ts로 변경하세요."background": { - "service_worker": "src/background.js", + "service_worker": "src/background.ts", "type": "module" }, @@ "matches": ["<all_urls>"], - "js": ["src/content.js"] + "js": ["src/content.ts"]🤖 Prompt for AI Agents