Update installation guide in main.py, enhance manual.html with translation notes, and add pomodoro settings in settings.html and settings.js. Improve zzim.html with new styling and options for user preferences.
This commit is contained in:
commit
220e7e42da
8
main.py
8
main.py
|
|
@ -131,9 +131,9 @@ class ExtensionInstaller(QWidget):
|
|||
|
||||
guide = QLabel(
|
||||
"<b>설치 안내:</b><br>"
|
||||
"<span style='color:#e1ffbb;'>1. main.py와 같은 경로의 <b>wrmc_ext</b> 폴더가 자동 복사됩니다.<br>"
|
||||
"2. 설치할 브라우저를 모두 체크 후 <b>설치 시작</b>을 누르세요.<br>"
|
||||
"3. 설치 후 브라우저별 단계별 안내를 참고하세요.</span>")
|
||||
# "<span style='color:#e1ffbb;'>1. main.py와 같은 경로의 <b>wrmc_ext</b> 폴더가 자동 복사됩니다.<br>"
|
||||
"1. 설치할 브라우저를 모두 체크 후 <b>설치 시작</b>을 누르세요.<br>"
|
||||
"2. 설치 후 브라우저별 단계별 안내를 참고하세요.</span>")
|
||||
guide.setWordWrap(True)
|
||||
guide.setStyleSheet("margin-bottom:12px;")
|
||||
layout.addWidget(guide)
|
||||
|
|
@ -256,7 +256,7 @@ class ExtensionInstaller(QWidget):
|
|||
row2.setTextFormat(Qt.RichText)
|
||||
row2.setWordWrap(True)
|
||||
# 3번 압축해제된 확장프로그램 로드
|
||||
row3 = QLabel("③ <b>[압축해제된 확장 프로그램 로드]</b> 버튼 클릭")
|
||||
row3 = QLabel("③ <b>[압축해제된 확장 프로그램 로드] 또는 [압축해제된 확장앱 설치]</b> 버튼 클릭")
|
||||
row3.setTextFormat(Qt.RichText)
|
||||
row3.setWordWrap(True)
|
||||
# 4번 폴더 선택 및 복사
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ chrome.runtime.onInstalled.addListener(() => {
|
|||
|
||||
chrome.alarms.create("keepAlive", { periodInMinutes: 4 });
|
||||
|
||||
// 새 어록 감지 알람 생성 (5분마다)
|
||||
chrome.alarms.create("checkNewSayings", { periodInMinutes: 5 });
|
||||
// 새 어록 감지 알람 생성 (1분마다)
|
||||
chrome.alarms.create("checkNewSayings", { periodInMinutes: 1 });
|
||||
|
||||
// 초기 마지막 확인 시간 설정
|
||||
chrome.storage.local.set({ lastSayingsCheck: Date.now() });
|
||||
|
|
@ -325,6 +325,9 @@ chrome.alarms.onAlarm.addListener((alarm) => {
|
|||
console.log("[background.js] 서비스 워커 유지 알람 실행됨");
|
||||
} else if (alarm.name === "checkNewSayings") {
|
||||
checkForNewSayings();
|
||||
} else if (alarm.name === "autoMutualZzim") {
|
||||
console.log("[background.js] autoMutualZzim 주기 실행");
|
||||
chrome.runtime.sendMessage({ action: "autoMutualZzim" });
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -2699,7 +2702,9 @@ class TimeAlarmManager {
|
|||
enabled: true,
|
||||
workTime: 60, // 분
|
||||
restTime: 5, // 분
|
||||
autoZzim: false
|
||||
autoZzim: false,
|
||||
pomodoro: false,
|
||||
cycle: 0 // 포모도로 현재 사이클(0~3)
|
||||
};
|
||||
this.startTime = null;
|
||||
}
|
||||
|
|
@ -2754,9 +2759,10 @@ class TimeAlarmManager {
|
|||
}
|
||||
|
||||
this.startTime = Date.now();
|
||||
const workTimeMs = this.settings.workTime * 60 * 1000;
|
||||
const workMin = this.settings.pomodoro ? 35 : this.settings.workTime;
|
||||
const workTimeMs = workMin * 60 * 1000;
|
||||
|
||||
console.log(`[TimeAlarm] 작업 타이머 시작: ${this.settings.workTime}분`);
|
||||
console.log(`[TimeAlarm] 작업 타이머 시작: ${workMin}분`);
|
||||
|
||||
this.workTimer = setTimeout(() => {
|
||||
this.showRestModal();
|
||||
|
|
@ -2787,7 +2793,8 @@ class TimeAlarmManager {
|
|||
}
|
||||
|
||||
startBreakTimer(popupId) {
|
||||
const breakTimeMs = this.settings.restTime * 60 * 1000;
|
||||
const breakTimeMin = this.settings.restTime;
|
||||
const breakTimeMs = breakTimeMin * 60 * 1000;
|
||||
|
||||
console.log(`[TimeAlarm] 휴식 타이머 시작: ${this.settings.restTime}분`);
|
||||
|
||||
|
|
@ -2802,6 +2809,11 @@ class TimeAlarmManager {
|
|||
// 작업 완료 알림
|
||||
this.showWorkCompleteNotification();
|
||||
|
||||
// 포모도로 모드: 사이클 증가 및 리셋
|
||||
if (this.settings.pomodoro) {
|
||||
this.settings.cycle = (this.settings.cycle + 1) % 4;
|
||||
}
|
||||
|
||||
// 다음 작업 타이머 시작
|
||||
this.startWorkTimer();
|
||||
|
||||
|
|
@ -2813,7 +2825,7 @@ class TimeAlarmManager {
|
|||
type: 'basic',
|
||||
iconUrl: 'icon.png',
|
||||
title: '휴식 시간입니다! 🧘♀️',
|
||||
message: `${this.settings.workTime}분간 수고하셨습니다. ${this.settings.restTime}분간 휴식을 취하세요.`
|
||||
message: `${this.settings.pomodoro ? 35 : this.settings.workTime}분간 수고하셨습니다. ${this.settings.restTime}분간 휴식을 취하세요.`
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -2876,7 +2888,7 @@ class TimeAlarmManager {
|
|||
// 남은 시간 계산
|
||||
const now = Date.now();
|
||||
const elapsed = now - this.startTime;
|
||||
const totalWorkTime = this.settings.workTime * 60 * 1000;
|
||||
const totalWorkTime = (this.settings.pomodoro ? 35 : this.settings.workTime) * 60 * 1000;
|
||||
const remainingTime = totalWorkTime - elapsed;
|
||||
|
||||
if (remainingTime <= 0) {
|
||||
|
|
@ -2889,7 +2901,7 @@ class TimeAlarmManager {
|
|||
return {
|
||||
isRunning: true,
|
||||
remainingTime: remainingTime,
|
||||
workTime: this.settings.workTime,
|
||||
workTime: this.settings.pomodoro ? 35 : this.settings.workTime,
|
||||
restTime: this.settings.restTime,
|
||||
startTime: this.startTime,
|
||||
elapsed: elapsed
|
||||
|
|
@ -3233,360 +3245,3 @@ function getBackendConfig() {
|
|||
}
|
||||
|
||||
// 검색 결과 개선을 위한 키워드 확장 함수
|
||||
|
||||
// 백그라운드 찜하기 실행 함수
|
||||
async function handleExecuteBackgroundZzim(message, sendResponse) {
|
||||
try {
|
||||
console.log('[Background] 백그라운드 찜하기 시작:', message);
|
||||
|
||||
const { market, zzimType, userId, accessToken } = message;
|
||||
|
||||
if (!market || !market.market_url) {
|
||||
throw new Error('마켓 정보가 없습니다.');
|
||||
}
|
||||
|
||||
// 백그라운드 탭 생성
|
||||
const tab = await chrome.tabs.create({
|
||||
url: market.market_url,
|
||||
active: false
|
||||
});
|
||||
|
||||
console.log('[Background] 백그라운드 탭 생성됨:', tab.id);
|
||||
|
||||
// 찜하기 스크립트 정의
|
||||
const zzimScript = function() {
|
||||
return new Promise((resolve) => {
|
||||
let zzimCount = 0;
|
||||
const maxZzim = 50; // 최대 찜할 개수
|
||||
let isRunning = true;
|
||||
|
||||
console.log('찜하기 스크립트 시작');
|
||||
|
||||
function findZzimButtons() {
|
||||
// 네이버 스마트스토어의 찜 버튼 선택자들
|
||||
const selectors = [
|
||||
'button[data-testid="wishlist-button"]:not([aria-pressed="true"])',
|
||||
'.zzim_button:not(.active)',
|
||||
'.wish_button:not(.active)',
|
||||
'button[class*="wish"]:not([class*="active"])',
|
||||
'button[aria-label*="찜"]:not([aria-pressed="true"])'
|
||||
];
|
||||
|
||||
let buttons = [];
|
||||
for (const selector of selectors) {
|
||||
buttons = document.querySelectorAll(selector);
|
||||
if (buttons.length > 0) {
|
||||
console.log(`찜 버튼 발견 (${selector}):`, buttons.length);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(buttons);
|
||||
}
|
||||
|
||||
function clickZzimButtons() {
|
||||
if (!isRunning || zzimCount >= maxZzim) {
|
||||
console.log('찜하기 중단:', { isRunning, zzimCount, maxZzim });
|
||||
return false;
|
||||
}
|
||||
|
||||
const zzimButtons = findZzimButtons();
|
||||
console.log('찾은 찜 버튼 개수:', zzimButtons.length);
|
||||
|
||||
if (zzimButtons.length === 0) {
|
||||
console.log('더 이상 찜할 상품이 없습니다.');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 최대 10개씩 찜하기
|
||||
const buttonsToClick = zzimButtons.slice(0, Math.min(10, maxZzim - zzimCount));
|
||||
|
||||
buttonsToClick.forEach((btn, index) => {
|
||||
setTimeout(() => {
|
||||
if (!isRunning) return;
|
||||
|
||||
try {
|
||||
// 버튼이 화면에 보이도록 스크롤
|
||||
btn.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
|
||||
setTimeout(() => {
|
||||
if (!isRunning) return;
|
||||
|
||||
btn.click();
|
||||
zzimCount++;
|
||||
console.log(`찜 버튼 클릭: ${zzimCount}개`);
|
||||
|
||||
// 클릭 후 잠시 대기
|
||||
setTimeout(() => {
|
||||
// 추가 확인 버튼이 있다면 클릭
|
||||
const confirmBtn = document.querySelector('button[data-testid="confirm"], .confirm_btn, button:contains("확인")');
|
||||
if (confirmBtn) {
|
||||
confirmBtn.click();
|
||||
}
|
||||
}, 200);
|
||||
|
||||
}, 300); // 스크롤 후 클릭까지 대기
|
||||
|
||||
} catch (e) {
|
||||
console.error('찜 버튼 클릭 오류:', e);
|
||||
}
|
||||
}, index * 800); // 0.8초 간격으로 클릭
|
||||
});
|
||||
|
||||
return buttonsToClick.length > 0;
|
||||
}
|
||||
|
||||
// 페이지 스크롤 및 찜하기 반복
|
||||
function scrollAndZzim() {
|
||||
if (!isRunning || zzimCount >= maxZzim) {
|
||||
console.log('찜하기 완료:', zzimCount);
|
||||
resolve(zzimCount);
|
||||
return;
|
||||
}
|
||||
|
||||
// 페이지 하단으로 스크롤
|
||||
window.scrollTo(0, document.body.scrollHeight);
|
||||
|
||||
// 스크롤 후 잠시 대기하여 새 상품 로드
|
||||
setTimeout(() => {
|
||||
if (clickZzimButtons()) {
|
||||
setTimeout(scrollAndZzim, 5000); // 5초 후 다시 시도
|
||||
} else {
|
||||
console.log('더 이상 찜할 상품이 없어 종료');
|
||||
resolve(zzimCount);
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// 30초 후 자동 종료
|
||||
setTimeout(() => {
|
||||
isRunning = false;
|
||||
console.log('시간 초과로 찜하기 종료');
|
||||
resolve(zzimCount);
|
||||
}, 30000);
|
||||
|
||||
// 시작
|
||||
console.log('찜하기 시작');
|
||||
setTimeout(scrollAndZzim, 1000); // 페이지 로드 후 1초 대기
|
||||
});
|
||||
};
|
||||
|
||||
// 페이지 로드 완료 후 스크립트 실행
|
||||
const executeZzim = (tabId, changeInfo) => {
|
||||
if (tabId === tab.id && changeInfo.status === 'complete') {
|
||||
chrome.tabs.onUpdated.removeListener(executeZzim);
|
||||
|
||||
console.log('[Background] 페이지 로드 완료, 찜하기 스크립트 실행');
|
||||
|
||||
chrome.scripting.executeScript({
|
||||
target: { tabId: tab.id },
|
||||
func: zzimScript
|
||||
}).then((results) => {
|
||||
const zzimCount = results[0]?.result || 0;
|
||||
console.log('[Background] 찜하기 완료:', zzimCount);
|
||||
|
||||
// 탭 닫기
|
||||
setTimeout(() => {
|
||||
chrome.tabs.remove(tab.id).catch(console.error);
|
||||
}, 2000);
|
||||
|
||||
sendResponse({
|
||||
success: true,
|
||||
zzimCount: zzimCount,
|
||||
message: `${zzimCount}개 상품을 찜했습니다.`
|
||||
});
|
||||
|
||||
}).catch((error) => {
|
||||
console.error('[Background] 찜하기 스크립트 실행 오류:', error);
|
||||
chrome.tabs.remove(tab.id).catch(console.error);
|
||||
sendResponse({
|
||||
success: false,
|
||||
error: '찜하기 스크립트 실행 실패: ' + error.message
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
chrome.tabs.onUpdated.addListener(executeZzim);
|
||||
|
||||
// 타임아웃 설정 (1분)
|
||||
setTimeout(() => {
|
||||
chrome.tabs.onUpdated.removeListener(executeZzim);
|
||||
chrome.tabs.remove(tab.id).catch(console.error);
|
||||
sendResponse({
|
||||
success: false,
|
||||
error: '찜하기 실행 시간 초과'
|
||||
});
|
||||
}, 60000);
|
||||
|
||||
} catch (error) {
|
||||
console.error('[Background] 백그라운드 찜하기 오류:', error);
|
||||
sendResponse({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 메시지 리스너 추가 - content.js에서 보내는 메시지 처리
|
||||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
console.log('[Background] 메시지 수신:', message);
|
||||
|
||||
// 지재권 검색 요청
|
||||
if (message.action === "searchTrademark") {
|
||||
console.log('[Background] 지재권 검색 요청 처리:', message.keyword);
|
||||
handleTrademarkSearch(message.keyword, sender.tab)
|
||||
.then(() => {
|
||||
sendResponse({ success: true });
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('[Background] 지재권 검색 처리 실패:', error);
|
||||
sendResponse({ success: false, error: error.message });
|
||||
});
|
||||
return true; // 비동기 응답
|
||||
}
|
||||
|
||||
// 멀티번역 요청
|
||||
if (message.action === "translateText") {
|
||||
console.log('[Background] 멀티번역 요청 처리:', message.text);
|
||||
handleMultiTranslate({ selectionText: message.text })
|
||||
.then(() => {
|
||||
sendResponse({ success: true });
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('[Background] 멀티번역 처리 실패:', error);
|
||||
sendResponse({ success: false, error: error.message });
|
||||
});
|
||||
return true; // 비동기 응답
|
||||
}
|
||||
|
||||
// 직번역 요청
|
||||
if (message.action === "handleDirectTranslation") {
|
||||
const selectedText = message.selectedText || message.text;
|
||||
console.log('[Background] 직번역 요청 처리:', selectedText);
|
||||
handleDirectTranslation(selectedText, sender.tab)
|
||||
.then(() => {
|
||||
sendResponse({ success: true });
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('[Background] 직번역 처리 실패:', error);
|
||||
sendResponse({ success: false, error: error.message });
|
||||
});
|
||||
return true; // 비동기 응답
|
||||
}
|
||||
|
||||
// 한중번역 요청
|
||||
if (message.action === "handleKoreanToChinese") {
|
||||
const selectedText = message.selectedText || message.text;
|
||||
console.log('[Background] 한중번역 요청 처리:', selectedText);
|
||||
handleKoreanToChinese(selectedText, sender.tab)
|
||||
.then(() => {
|
||||
sendResponse({ success: true });
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('[Background] 한중번역 처리 실패:', error);
|
||||
sendResponse({ success: false, error: error.message });
|
||||
});
|
||||
return true; // 비동기 응답
|
||||
}
|
||||
|
||||
// 금지어 추가 요청
|
||||
if (message.action === "addBannedWord") {
|
||||
handleAddBannedWord(message, sendResponse);
|
||||
return true; // 비동기 응답
|
||||
}
|
||||
});
|
||||
|
||||
// 직번역 처리 함수 (선택된 텍스트를 바로 번역된 텍스트로 대체)
|
||||
async function handleDirectTranslation(selectedText, tab) {
|
||||
if (!selectedText) {
|
||||
chrome.notifications.create({
|
||||
type: 'basic',
|
||||
iconUrl: 'icon.png',
|
||||
title: '텍스트 선택 필요',
|
||||
message: '번역할 텍스트를 먼저 선택해주세요.'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[background.js] 직번역 요청:', selectedText);
|
||||
|
||||
try {
|
||||
// 언어 감지 및 번역 방향 결정
|
||||
const isKorean = /[ㄱ-ㅎ|ㅏ-ㅣ|가-힣]/.test(selectedText);
|
||||
const isChinese = /[\u4e00-\u9fff]/.test(selectedText);
|
||||
const isEnglish = /^[a-zA-Z\s.,!?'"()-]+$/.test(selectedText.trim());
|
||||
|
||||
let translatedText;
|
||||
let direction;
|
||||
|
||||
if (isKorean && !isChinese) {
|
||||
// 한국어 → 중국어
|
||||
translatedText = await translateText(selectedText, 'ko', 'zh');
|
||||
direction = '한국어 → 중국어';
|
||||
} else if (isChinese && !isKorean) {
|
||||
// 중국어 → 한국어
|
||||
translatedText = await translateText(selectedText, 'zh', 'ko');
|
||||
direction = '중국어 → 한국어';
|
||||
} else if (isEnglish && !isKorean && !isChinese) {
|
||||
// 영어 → 한국어
|
||||
translatedText = await translateText(selectedText, 'en', 'ko');
|
||||
direction = '영어 → 한국어';
|
||||
} else if (isKorean && isChinese) {
|
||||
// 한국어와 중국어가 섞여있는 경우 - 한국어 비율로 판단
|
||||
const koreanChars = (selectedText.match(/[ㄱ-ㅎ|ㅏ-ㅣ|가-힣]/g) || []).length;
|
||||
const chineseChars = (selectedText.match(/[\u4e00-\u9fff]/g) || []).length;
|
||||
|
||||
if (koreanChars >= chineseChars) {
|
||||
// 한국어가 더 많으면 중국어로 번역
|
||||
translatedText = await translateText(selectedText, 'ko', 'zh');
|
||||
direction = '한국어 → 중국어';
|
||||
} else {
|
||||
// 중국어가 더 많으면 한국어로 번역
|
||||
translatedText = await translateText(selectedText, 'zh', 'ko');
|
||||
direction = '중국어 → 한국어';
|
||||
}
|
||||
} else {
|
||||
// 기타 언어는 한국어로 번역
|
||||
translatedText = await translateText(selectedText, 'auto', 'ko');
|
||||
direction = '자동감지 → 한국어';
|
||||
}
|
||||
|
||||
// 선택된 텍스트를 번역된 텍스트로 대체
|
||||
console.log('[background.js] replaceSelectedText 실행 시작, 번역된 텍스트:', translatedText);
|
||||
|
||||
try {
|
||||
const result = await chrome.scripting.executeScript({
|
||||
target: { tabId: tab.id },
|
||||
function: replaceSelectedText,
|
||||
args: [translatedText]
|
||||
});
|
||||
|
||||
console.log('[background.js] replaceSelectedText 실행 결과:', result);
|
||||
|
||||
if (result && result[0] && result[0].result !== undefined) {
|
||||
console.log('[background.js] 스크립트 실행 성공, 반환값:', result[0].result);
|
||||
} else {
|
||||
console.log('[background.js] 스크립트 실행 완료, 반환값 없음');
|
||||
}
|
||||
} catch (scriptError) {
|
||||
console.error('[background.js] replaceSelectedText 실행 중 오류:', scriptError);
|
||||
}
|
||||
|
||||
chrome.notifications.create({
|
||||
type: 'basic',
|
||||
iconUrl: 'icon.png',
|
||||
title: '직번역 완료',
|
||||
message: `${direction}로 번역되어 텍스트가 대체되었습니다.`
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('[background.js] 직번역 중 오류:', error);
|
||||
chrome.notifications.create({
|
||||
type: 'basic',
|
||||
iconUrl: 'icon.png',
|
||||
title: '직번역 오류',
|
||||
message: '번역 중 문제가 발생했습니다. 다시 시도해 주세요.'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -21,6 +21,7 @@
|
|||
"*://smartstore.naver.com/*",
|
||||
"*://translate.googleapis.com/*",
|
||||
"*://api.mymemory.translated.net/*",
|
||||
"https://market.m.taobao.com/*",
|
||||
"*://openapi.naver.com/*",
|
||||
"*://api-free.deepl.com/*",
|
||||
"*://api.deepl.com/*",
|
||||
|
|
@ -34,6 +35,7 @@
|
|||
{
|
||||
"matches": ["<all_urls>"],
|
||||
"js": ["content.js"],
|
||||
"all_frames": true,
|
||||
"run_at": "document_end"
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -330,7 +330,7 @@
|
|||
<ul class="feature-list">
|
||||
<li>번역할 문장이나 단어를 드래그 후 단축키(컨트롤+쉬프트+Z) 입력</li>
|
||||
<li>웹페이지에서 바로 한글과 중국어를 빠르게 번역</li>
|
||||
<li>위챗등의 대화에서 한글 입력 후 선택 및 단축키로 바로 번역</li>
|
||||
<li>위챗등의 대화에서 한글 입력 후 선택 및 단축키로 바로 번역[현재 위챗의 특수성으로 적용되지않음]</li>
|
||||
<li>웹페이지에서 중국어를 드래그 후 단축키로 바로 번역 결과 확인</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
@ -343,6 +343,7 @@
|
|||
<li>휴식시간 추천 활동</li>
|
||||
<li>실시간 타이머 알림</li>
|
||||
<li>개인화된 시간 설정</li>
|
||||
<li></li>[포모도로 타이머가 활성화 되면 35분/5분으로 기본설정되며 4사이클 총180분(3시간) 휴식시간 추천 활동 제공]</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -542,21 +542,29 @@
|
|||
<div class="toggle-switch" id="timeAlarmToggle"></div>
|
||||
<span>알람 활성화/비활성화</span>
|
||||
</div>
|
||||
|
||||
<!-- 포모도로 토글 -->
|
||||
<div class="time-input-group">
|
||||
<label>포모도로:</label>
|
||||
<div class="toggle-switch" id="pomodoroToggle"></div>
|
||||
<span>35분 작업 / 5분 휴식 (4회) 고정</span>
|
||||
</div>
|
||||
|
||||
<div class="time-input-group">
|
||||
<label>작업 시간:</label>
|
||||
<input type="number" id="workTimeInput" min="1" max="480" value="60">
|
||||
<input type="number" id="workTimeInput" min="60" max="480" value="60">
|
||||
<span>분 (작업 후 휴식 알림)</span>
|
||||
</div>
|
||||
|
||||
<div class="time-input-group">
|
||||
<label>휴식 시간:</label>
|
||||
<input type="number" id="restTimeInput" min="1" max="60" value="5">
|
||||
<input type="number" id="restTimeInput" min="1" max="15" value="5">
|
||||
<span>분 (휴식 시간 길이)</span>
|
||||
</div>
|
||||
|
||||
<div class="checkbox-group">
|
||||
<input type="checkbox" id="autoZzimCheckbox" disabled>
|
||||
<!-- <input type="checkbox" id="autoZzimCheckbox" disabled> -->
|
||||
<input type="checkbox" id="autoZzimCheckbox">
|
||||
<label for="autoZzimCheckbox">휴식 중 자동 찜 기능 활성화</label>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -236,7 +236,9 @@ class SettingsManager {
|
|||
enabled: true,
|
||||
workTime: 60, // 분
|
||||
restTime: 5, // 분
|
||||
autoZzim: false
|
||||
autoZzim: false,
|
||||
pomodoro: false,
|
||||
cycle: 0
|
||||
};
|
||||
console.log('시간 알람 설정 로드 완료:', this.timeAlarmSettings);
|
||||
} catch (error) {
|
||||
|
|
@ -245,7 +247,9 @@ class SettingsManager {
|
|||
enabled: true,
|
||||
workTime: 60,
|
||||
restTime: 5,
|
||||
autoZzim: false
|
||||
autoZzim: false,
|
||||
pomodoro: false,
|
||||
cycle: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -321,6 +325,7 @@ class SettingsManager {
|
|||
const workTimeInput = document.getElementById('workTimeInput');
|
||||
const restTimeInput = document.getElementById('restTimeInput');
|
||||
const autoZzimCheckbox = document.getElementById('autoZzimCheckbox');
|
||||
const pomodoroToggle = document.getElementById('pomodoroToggle');
|
||||
|
||||
if (workTimeInput) {
|
||||
workTimeInput.addEventListener('change', (e) => {
|
||||
|
|
@ -339,6 +344,18 @@ class SettingsManager {
|
|||
this.timeAlarmSettings.autoZzim = e.target.checked;
|
||||
});
|
||||
}
|
||||
|
||||
if (pomodoroToggle) {
|
||||
pomodoroToggle.addEventListener('click', () => {
|
||||
this.timeAlarmSettings.pomodoro = !this.timeAlarmSettings.pomodoro;
|
||||
// 포모도로가 활성화되면 고정 값 적용
|
||||
if (this.timeAlarmSettings.pomodoro) {
|
||||
this.timeAlarmSettings.workTime = 35;
|
||||
this.timeAlarmSettings.restTime = 5;
|
||||
}
|
||||
this.updateTimeAlarmUI();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
updateUI() {
|
||||
|
|
@ -388,12 +405,23 @@ class SettingsManager {
|
|||
}
|
||||
}
|
||||
|
||||
const pomodoroToggle = document.getElementById('pomodoroToggle');
|
||||
if (pomodoroToggle) {
|
||||
if (this.timeAlarmSettings.pomodoro) {
|
||||
pomodoroToggle.classList.add('active');
|
||||
} else {
|
||||
pomodoroToggle.classList.remove('active');
|
||||
}
|
||||
}
|
||||
|
||||
if (workTimeInput) {
|
||||
workTimeInput.value = this.timeAlarmSettings.workTime;
|
||||
workTimeInput.disabled = this.timeAlarmSettings.pomodoro;
|
||||
}
|
||||
|
||||
if (restTimeInput) {
|
||||
restTimeInput.value = this.timeAlarmSettings.restTime;
|
||||
restTimeInput.disabled = this.timeAlarmSettings.pomodoro;
|
||||
}
|
||||
|
||||
if (autoZzimCheckbox) {
|
||||
|
|
@ -412,6 +440,12 @@ class SettingsManager {
|
|||
this.showLoading(true);
|
||||
|
||||
// 시간 알람 설정 업데이트
|
||||
if (this.timeAlarmSettings.pomodoro) {
|
||||
// 강제 고정 값
|
||||
this.timeAlarmSettings.workTime = 35;
|
||||
this.timeAlarmSettings.restTime = 5;
|
||||
}
|
||||
|
||||
const workTimeInput = document.getElementById('workTimeInput');
|
||||
const restTimeInput = document.getElementById('restTimeInput');
|
||||
const autoZzimCheckbox = document.getElementById('autoZzimCheckbox');
|
||||
|
|
@ -484,10 +518,12 @@ class SettingsManager {
|
|||
|
||||
// 시간 알람 설정 초기화
|
||||
this.timeAlarmSettings = {
|
||||
enabled: false,
|
||||
enabled: true,
|
||||
workTime: 60,
|
||||
restTime: 5,
|
||||
autoZzim: false
|
||||
autoZzim: false,
|
||||
pomodoro: false,
|
||||
cycle: 0
|
||||
};
|
||||
|
||||
// 저장소에서 시간 알람 설정 제거
|
||||
|
|
|
|||
|
|
@ -169,6 +169,37 @@
|
|||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.market-item:hover::before {
|
||||
opacity: 1;
|
||||
border: 1px solid #e1e8ed;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin-bottom: 15px;
|
||||
background: white;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.market-item:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.market-item::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4px;
|
||||
background: linear-gradient(90deg, #667eea, #764ba2);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.market-item:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
|
@ -180,10 +211,17 @@
|
|||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.market-checkbox {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.market-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
|
|
@ -205,6 +243,7 @@
|
|||
.market-info {
|
||||
flex: 1;
|
||||
margin: 0 15px;
|
||||
margin: 0 15px;
|
||||
}
|
||||
|
||||
.market-name {
|
||||
|
|
@ -212,6 +251,7 @@
|
|||
color: #2c3e50;
|
||||
margin-bottom: 5px;
|
||||
font-size: 16px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.market-nickname {
|
||||
|
|
@ -219,6 +259,9 @@
|
|||
font-size: 14px;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.market-url {
|
||||
|
|
@ -270,6 +313,57 @@
|
|||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
|
||||
.created-date {
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
background: #f8f9fa;
|
||||
padding: 4px 8px;
|
||||
border-radius: 12px;
|
||||
background: #f8f9fa;
|
||||
padding: 6px 10px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #e1e8ed;
|
||||
margin-bottom: 10px;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
.market-stats {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.zzim-count {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
background: #e8f4f8;
|
||||
padding: 4px 8px;
|
||||
border-radius: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.visibility-status {
|
||||
font-size: 12px;
|
||||
padding: 4px 10px;
|
||||
border-radius: 12px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.visibility-status.visible {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
|
||||
.visibility-status.hidden {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
|
||||
.created-date {
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
|
|
@ -293,6 +387,22 @@
|
|||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.market-actions .btn:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||
gap: 8px;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.market-actions .btn {
|
||||
padding: 8px 12px;
|
||||
font-size: 12px;
|
||||
border-radius: 6px;
|
||||
min-width: 70px;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.market-actions .btn:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||
|
|
@ -590,6 +700,257 @@
|
|||
color: #666;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
/* 모달 스타일 */
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(3px);
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: white;
|
||||
margin: 5% auto;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
width: 90%;
|
||||
max-width: 500px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
||||
animation: slideIn 0.3s ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 20px 25px;
|
||||
border-bottom: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: none;
|
||||
border: none;
|
||||
color: white;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.modal-close:hover {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
width: 100%;
|
||||
padding: 12px 15px;
|
||||
border: 2px solid #e1e8ed;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
transition: all 0.3s ease;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-input:focus {
|
||||
outline: none;
|
||||
border-color: #667eea;
|
||||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
min-height: 80px;
|
||||
resize: vertical;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
padding: 20px 25px;
|
||||
border-top: 1px solid #e1e8ed;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.btn-modal {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.btn-modal-cancel {
|
||||
background-color: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-modal-cancel:hover {
|
||||
background-color: #5a6268;
|
||||
}
|
||||
|
||||
.btn-modal-save {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-modal-save:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
.btn-modal-save:disabled {
|
||||
background: #cccccc;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-50px) scale(0.9);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* 체크박스 스타일링 */
|
||||
.checkbox-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: 15px;
|
||||
padding: 15px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
border: 2px solid #e1e8ed;
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
|
||||
.checkbox-container:hover {
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.custom-checkbox {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.custom-checkbox input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.checkbox-checkmark {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
background-color: white;
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.custom-checkbox:hover input ~ .checkbox-checkmark {
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.custom-checkbox input:checked ~ .checkbox-checkmark {
|
||||
background-color: #667eea;
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.checkbox-checkmark:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.custom-checkbox input:checked ~ .checkbox-checkmark:after {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.custom-checkbox .checkbox-checkmark:after {
|
||||
left: 6px;
|
||||
top: 2px;
|
||||
width: 6px;
|
||||
height: 10px;
|
||||
border: solid white;
|
||||
border-width: 0 2px 2px 0;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.checkbox-description {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-top: 5px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -693,8 +1054,66 @@
|
|||
체크하면 전체상품(/category/ALL) 대신 최신상품(/best)부터 찜합니다.
|
||||
</div>
|
||||
</div>
|
||||
💝 찜하기 작업
|
||||
</div>
|
||||
|
||||
<!-- 백그라운드 실행 옵션 추가 -->
|
||||
<div style="margin-bottom: 20px; padding: 15px; background: #f8f9fa; border-radius: 6px;">
|
||||
<label style="display: flex; align-items: center; gap: 10px; cursor: pointer;">
|
||||
<input type="checkbox" id="background-mode" style="transform: scale(1.2);">
|
||||
<span style="font-weight: bold; color: #2c3e50;">🔄 백그라운드 모드</span>
|
||||
</label>
|
||||
<div style="font-size: 12px; color: #666; margin-top: 5px; margin-left: 25px;">
|
||||
체크하면 새 탭에서 백그라운드로 찜하기를 실행합니다.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 찜하기 옵션 설정 -->
|
||||
<div style="margin-bottom: 20px; padding: 20px; background: #f8f9fa; border-radius: 8px; border: 2px solid #e1e8ed;">
|
||||
<h4 style="margin: 0 0 15px 0; color: #2c3e50; font-size: 16px;">⚙️ 찜하기 옵션</h4>
|
||||
|
||||
<!-- 찜하기 속도 설정 -->
|
||||
<div style="margin-bottom: 15px;">
|
||||
<label style="display: block; margin-bottom: 8px; font-weight: 600; color: #2c3e50; font-size: 14px;">
|
||||
⏱️ 찜하기 속도 (찜 간격)
|
||||
</label>
|
||||
<div style="display: flex; align-items: center; gap: 10px; flex-wrap: wrap;">
|
||||
<div style="display: flex; align-items: center; gap: 5px;">
|
||||
<span style="font-size: 12px; color: #666;">기본 간격:</span>
|
||||
<input type="number" id="base-delay" value="1000" min="1000" max="5000" step="100"
|
||||
style="width: 80px; padding: 4px 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px;"
|
||||
disabled>
|
||||
<span style="font-size: 12px; color: #666;">ms</span>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: 5px;">
|
||||
<span style="font-size: 12px; color: #666;">+ 추가 간격:</span>
|
||||
<input type="number" id="user-delay" value="0" min="0" max="9000" step="100"
|
||||
style="width: 80px; padding: 4px 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px;">
|
||||
<span style="font-size: 12px; color: #666;">ms</span>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: 5px;">
|
||||
<span style="font-size: 12px; color: #666;">= 총 간격:</span>
|
||||
<span id="total-delay" style="font-weight: bold; color: #667eea;">1000ms</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="font-size: 11px; color: #888; margin-top: 5px;">
|
||||
💡 찜과 찜 사이의 대기 시간을 설정합니다. (1초~10초)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 최신상품 우선 찜하기 옵션 -->
|
||||
<div style="margin-bottom: 10px;">
|
||||
<label style="display: flex; align-items: center; gap: 10px; cursor: pointer;">
|
||||
<input type="checkbox" id="latest-first" style="transform: scale(1.2);">
|
||||
<span style="font-weight: 600; color: #2c3e50;">🆕 최신상품 우선 찜하기</span>
|
||||
</label>
|
||||
<div style="font-size: 12px; color: #666; margin-top: 5px; margin-left: 25px;">
|
||||
체크하면 전체상품(/category/ALL) 대신 최신상품(/best)부터 찜합니다.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="action-buttons">
|
||||
<button class="btn btn-success btn-large" id="my-market-zzim-btn">
|
||||
💝 내 마켓 찜하기
|
||||
|
|
@ -716,8 +1135,23 @@
|
|||
<div style="width: 100%; height: 8px; background: #e0e0e0; border-radius: 4px; overflow: hidden;">
|
||||
<div id="progress-bar" style="height: 100%; background: #3498db; width: 0%; transition: width 0.3s ease;"></div>
|
||||
</div>
|
||||
<button class="btn btn-danger btn-large" id="stop-zzim-btn" style="display: none;">
|
||||
⏹️ 찜하기 중단
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 진행률 표시 -->
|
||||
<div id="zzim-progress" style="display: none; margin: 20px 0;">
|
||||
<div style="display: flex; justify-content: space-between; margin-bottom: 5px;">
|
||||
<span id="progress-text">진행 중...</span>
|
||||
<span id="progress-percent">0%</span>
|
||||
</div>
|
||||
<div style="width: 100%; height: 8px; background: #e0e0e0; border-radius: 4px; overflow: hidden;">
|
||||
<div id="progress-bar" style="height: 100%; background: #3498db; width: 0%; transition: width 0.3s ease;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="zzim-status" class="loading" style="display: none;"></div>
|
||||
<div id="zzim-error" class="error" style="display: none;"></div>
|
||||
<div id="zzim-success" class="success" style="display: none;"></div>
|
||||
|
|
|
|||
729
wrmc_ext/zzim.js
729
wrmc_ext/zzim.js
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue