SearchTrademark/sayings.js

1699 lines
57 KiB
JavaScript

// 어록 관리 모듈
class SayingsManager {
constructor() {
// 초기값 설정 (chrome.storage에서 로드될 때까지 임시)
this.SUPABASE_URL = null;
this.SUPABASE_ANON_KEY = null;
this.DEBUG_MODE = true;
this.ACCESS_TOKEN = null;
this.isConfigLoaded = false;
// 필터 상태
this.currentFilters = {
category: 'all',
dateRange: 'all',
searchText: ''
};
// 원본 데이터 저장
this.allSayings = [];
this.filteredSayings = [];
// 새 어록 감지를 위한 변수들
this.lastCheckTime = null;
this.newSayingsCheckInterval = null;
this.isCheckingNewSayings = false;
this.debugLog('SayingsManager 생성자 시작 - 설정 로드 대기 중');
// 필터 이벤트 등록
this.attachFilterEvents();
this.debugLog('SayingsManager 생성자 완료');
// ESC 키로 창 닫기 이벤트 등록
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") {
// 현재 창이 어록 관리 창인 경우 닫기
if (window.location.href.includes('sayings.html')) {
window.close();
e.preventDefault();
}
}
});
}
// 설정 로드 함수
async loadConfig() {
try {
this.debugLog('chrome.storage에서 설정 로드 시작');
// 1. sayings_config 우선 확인
const configData = await chrome.storage.local.get('sayings_config');
if (configData.sayings_config) {
const config = configData.sayings_config;
this.debugLog('sayings_config에서 설정 로드', {
hasUrl: !!config.SUPABASE_URL,
hasKey: !!config.SUPABASE_ANON_KEY,
hasToken: !!config.ACCESS_TOKEN,
debugMode: config.DEBUG_MODE,
timestamp: config.timestamp,
age: Date.now() - config.timestamp
});
// 설정이 너무 오래된 경우 (5분 이상) 경고
if (Date.now() - config.timestamp > 5 * 60 * 1000) {
this.debugLog('⚠️ 설정이 오래되었습니다', { age: Date.now() - config.timestamp });
}
this.SUPABASE_URL = config.SUPABASE_URL;
this.SUPABASE_ANON_KEY = config.SUPABASE_ANON_KEY;
this.DEBUG_MODE = config.DEBUG_MODE !== undefined ? config.DEBUG_MODE : true;
this.ACCESS_TOKEN = config.ACCESS_TOKEN;
} else {
// 2. 개별 설정 확인 (fallback)
this.debugLog('sayings_config가 없음, 개별 설정 확인 중');
const storageData = await chrome.storage.local.get([
'access_token',
'SUPABASE_URL',
'SUPABASE_ANON_KEY'
]);
this.debugLog('개별 설정 조회 결과', {
hasToken: !!storageData.access_token,
hasUrl: !!storageData.SUPABASE_URL,
hasKey: !!storageData.SUPABASE_ANON_KEY
});
// 기본값 설정
this.SUPABASE_URL = storageData.SUPABASE_URL || 'http://146.56.101.199:8000';
this.SUPABASE_ANON_KEY = storageData.SUPABASE_ANON_KEY || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ey AgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE';
this.ACCESS_TOKEN = storageData.access_token;
}
this.isConfigLoaded = true;
this.debugLog('설정 로드 완료', {
SUPABASE_URL: this.SUPABASE_URL,
hasToken: !!this.ACCESS_TOKEN,
tokenLength: this.ACCESS_TOKEN ? this.ACCESS_TOKEN.length : 0,
DEBUG_MODE: this.DEBUG_MODE,
isConfigLoaded: this.isConfigLoaded
});
// 설정 검증
if (!this.SUPABASE_URL) {
throw new Error('SUPABASE_URL이 설정되지 않았습니다');
}
if (!this.SUPABASE_ANON_KEY) {
throw new Error('SUPABASE_ANON_KEY가 설정되지 않았습니다');
}
if (!this.ACCESS_TOKEN) {
throw new Error('ACCESS_TOKEN이 없습니다. 로그인이 필요합니다');
}
return true;
} catch (error) {
this.debugLog('설정 로드 실패', { error: error.message });
throw error;
}
}
// 디버그 로깅 함수
debugLog(message, data = null) {
if (this.DEBUG_MODE) {
console.log(`[Sayings] ${message}`, data || '');
this.updateDebugUI(`[${new Date().toLocaleTimeString()}] ${message}`);
}
}
// 디버그 UI 업데이트
updateDebugUI(message) {
const debugElement = document.getElementById('debug-info');
if (debugElement) {
if (this.DEBUG_MODE) {
// 디버그 모드일 때만 표시
const existingButton = debugElement.querySelector('button');
const buttonHtml = existingButton ? existingButton.outerHTML : '';
debugElement.innerHTML = `${message}<br>${buttonHtml}`;
debugElement.style.display = "block";
// 버튼 이벤트 다시 등록
if (existingButton) {
const newButton = debugElement.querySelector('button');
if (newButton && !newButton.onclick) {
newButton.addEventListener('click', this.showDebugLogs.bind(this));
}
}
} else {
// 디버그 모드가 아닐 때는 숨김
debugElement.style.display = "none";
debugElement.innerHTML = "";
}
}
}
// 초기화 및 데이터 로드
async initialize() {
try {
this.debugLog('SayingsManager 초기화 시작');
// 1) 설정 로드 (chrome.storage에서)
this.debugLog('설정 로드 중...');
await this.loadConfig();
// 2) 토큰 유효성 검증
this.debugLog('토큰 유효성 검증 중...');
await this.validateToken();
// 3) 현재 사용자 정보 로드
this.debugLog('현재 사용자 정보 로드 중...');
await this.loadCurrentUser();
// 4) 어록 등록 모달 이벤트 등록
this.debugLog('어록 등록 모달 이벤트 등록 중...');
this.attachAddSayingEvents();
// 5) 카테고리와 타겟 옵션 로드
this.debugLog('카테고리와 타겟 옵션 로드 중...');
await this.loadCategoryAndTargetOptions();
// 6) 어록 목록 로드
this.debugLog('어록 목록 로드 시작');
await this.loadSayings();
// 7) 새 어록 감지 시작
this.debugLog('새 어록 감지 기능 시작');
this.startNewSayingsMonitoring();
this.debugLog('SayingsManager 초기화 완료');
} catch (error) {
this.debugLog('초기화 실패', { error: error.message });
this.renderError(error.message);
}
}
// 토큰 유효성 검증
async validateToken() {
try {
this.debugLog('토큰 검증 API 호출 준비', {
url: `${this.SUPABASE_URL}/auth/v1/user`,
hasToken: !!this.ACCESS_TOKEN,
tokenLength: this.ACCESS_TOKEN ? this.ACCESS_TOKEN.length : 0
});
const authRes = await fetch(`${this.SUPABASE_URL}/auth/v1/user`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${this.ACCESS_TOKEN}`,
'apikey': this.SUPABASE_ANON_KEY,
'Content-Type': 'application/json',
'Accept': 'application/json'
},
mode: 'cors',
credentials: 'omit'
});
this.debugLog('토큰 검증 API 응답', {
status: authRes.status,
statusText: authRes.statusText,
ok: authRes.ok
});
if (!authRes.ok) {
const errorText = await authRes.text();
this.debugLog('토큰 검증 실패', {
status: authRes.status,
error: errorText
});
throw new Error(`토큰이 유효하지 않습니다 (${authRes.status}: ${authRes.statusText})`);
}
const userData = await authRes.json();
this.debugLog('토큰 검증 성공', {
email: userData.email,
id: userData.id
});
return userData;
} catch (error) {
this.debugLog('토큰 검증 중 오류', {
errorName: error.name,
errorMessage: error.message
});
if (error.name === 'TypeError' && error.message.includes('fetch')) {
throw new Error(`네트워크 연결 오류: 서버에 연결할 수 없습니다.`);
}
throw error;
}
}
// 어록 목록 로드
async loadSayings() {
this.debugLog('어록 로딩 시작');
try {
// 로딩 표시
document.getElementById('sayings-container').innerHTML = '<div class="loading">📚 어록을 불러오는 중...</div>';
// API 호출을 위한 헤더 설정
const headers = {
'Authorization': `Bearer ${this.ACCESS_TOKEN}`,
'apikey': this.SUPABASE_ANON_KEY,
'Accept': 'application/json',
'Content-Type': 'application/json'
};
// 승인된 어록만 가져오는 쿼리
const query = `${this.SUPABASE_URL}/rest/v1/tanya_sayings?admin_approval=eq.true&order=created_at.desc`;
this.debugLog('어록 API 호출', {
url: query,
headers: Object.keys(headers)
});
const response = await fetch(query, {
method: 'GET',
headers: headers
});
if (!response.ok) {
const errorText = await response.text();
this.debugLog('어록 로딩 실패', {
status: response.status,
statusText: response.statusText,
error: errorText
});
throw new Error(`어록 로딩 실패: ${response.status} ${response.statusText}\n${errorText}`);
}
const sayings = await response.json();
this.debugLog('어록 로딩 성공', {
count: sayings.length,
firstSaying: sayings.length > 0 ? sayings[0].saying_title : 'N/A'
});
// 데이터 저장
this.allSayings = sayings;
this.filteredSayings = [...sayings];
// 필터 이벤트 연결
this.attachFilterEvents();
// 어록 표시
await this.displaySayings();
} catch (error) {
this.debugLog('어록 로딩 에러', { error: error.message });
this.renderError('어록을 불러오는데 실패했습니다', error.message);
}
}
// 어록 표시
async displaySayings() {
this.debugLog('어록 표시 시작');
const container = document.getElementById('sayings-container');
const stats = this.calculateStats();
// 통계 업데이트
const statsText = `${stats.total}개의 어록 (${Object.entries(stats.byCategory).map(([cat, count]) => `${cat}: ${count}`).join(', ')})`;
document.getElementById('sayings-stats').textContent = statsText;
this.debugLog('어록 통계 계산 완료', stats);
if (this.filteredSayings.length === 0) {
container.innerHTML = '<div class="empty-state">📝 표시할 어록이 없습니다.</div>';
this.debugLog('표시할 어록 없음');
return;
}
// 어록 카드 생성 (비동기 처리)
const sayingCards = await Promise.all(this.filteredSayings.map(async saying => {
// Markdown을 HTML로 변환 (비동기)
const contentHtml = await this.convertMarkdownToHtml(saying.saying);
// 날짜 포맷팅
const date = new Date(saying.created_at);
const formattedDate = date.toLocaleDateString('ko-KR', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
});
// 등록자 정보 (register 필드 사용)
const authorName = saying.register || saying.user_name || '알 수 없음';
return `
<div class="saying-card" data-category="${saying.cat}" data-target="${saying.target}">
<div class="saying-header">
<h3 class="saying-title">${this.escapeHtml(saying.saying_title)}</h3>
<div class="saying-meta">
<span class="saying-category">${this.escapeHtml(saying.cat)}</span>
<span class="saying-target">${this.escapeHtml(saying.target)}</span>
</div>
</div>
<div class="saying-content">
${contentHtml}
</div>
<div class="saying-footer">
<span class="saying-author">👤 ${this.escapeHtml(authorName)}</span>
<span class="saying-date">📅 ${formattedDate}</span>
</div>
</div>
`;
}));
container.innerHTML = sayingCards.join('');
this.debugLog('어록 표시 완료', {
filteredCount: this.filteredSayings.length,
cardsGenerated: sayingCards.length
});
}
// 마크다운을 HTML로 변환하는 함수 (비동기)
async convertMarkdownToHtml(content) {
// 빈 내용 체크
if (!content || typeof content !== 'string') {
console.warn('⚠️ 변환할 마크다운 내용이 없습니다');
return '';
}
try {
// 마크다운 라이브러리가 로드되었는지 확인
if (typeof marked !== 'undefined' && typeof DOMPurify !== 'undefined') {
console.log('✅ 마크다운 라이브러리 사용 가능');
// marked로 마크다운을 HTML로 변환
const rawHtml = marked.parse(content);
// DOMPurify로 HTML 정화
const cleanHtml = DOMPurify.sanitize(rawHtml);
console.log('🎯 마크다운 변환 완료');
return cleanHtml;
} else {
console.warn('⚠️ 마크다운 라이브러리가 로드되지 않음, 기본 처리 사용');
return this.basicMarkdownToHtml(content);
}
} catch (error) {
console.error('❌ 마크다운 변환 중 오류:', error);
// 오류 발생 시 기본 마크다운 처리로 폴백
return this.basicMarkdownToHtml(content);
}
}
// 기본 마크다운 처리 (라이브러리 없이) - 개선된 버전
basicMarkdownToHtml(markdown) {
let html = this.escapeHtml(markdown);
// 기본적인 마크다운 문법 처리 (순서 중요)
html = html
// 코드 블록 (```)
.replace(/```([\s\S]*?)```/g, '<pre><code>$1</code></pre>')
// 인라인 코드 (`)
.replace(/`([^`]+)`/g, '<code>$1</code>')
// 볼드 텍스트 (**)
.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
// 이탤릭 텍스트 (*)
.replace(/\*([^*]+)\*/g, '<em>$1</em>')
// 헤더 (###, ##, #)
.replace(/^### (.+)$/gm, '<h3>$1</h3>')
.replace(/^## (.+)$/gm, '<h2>$1</h2>')
.replace(/^# (.+)$/gm, '<h1>$1</h1>')
// 인용문 (>)
.replace(/^> (.+)$/gm, '<blockquote>$1</blockquote>')
// 링크 [텍스트](URL)
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>')
// 리스트 (- 또는 *)
.replace(/^[*-] (.+)$/gm, '<li>$1</li>')
// 줄바꿈 처리
.replace(/\n\n/g, '</p><p>')
.replace(/\n/g, '<br>');
// 리스트 태그로 감싸기
html = html.replace(/(<li>.*<\/li>)/gs, '<ul>$1</ul>');
// 단락 태그로 감싸기 (이미 p 태그가 있는 경우 제외)
if (!html.includes('<p>') && !html.includes('<h1>') && !html.includes('<h2>') && !html.includes('<h3>')) {
html = '<p>' + html + '</p>';
}
this.debugLog('기본 마크다운 변환 완료', {
원본길이: markdown.length,
변환길이: html.length
});
return html;
}
// HTML 이스케이프
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// 에러 렌더링
renderError(message) {
const container = document.getElementById("sayings-container");
const statsDiv = document.getElementById("sayings-stats");
if (container) {
container.innerHTML = `
<div class="empty-state">
<div class="icon">❌</div>
<div class="title">오류가 발생했습니다</div>
<div class="description">${message}</div>
</div>
`;
}
if (statsDiv) {
statsDiv.innerHTML = `<div style="color: red;">오류: ${message}</div>`;
}
this.debugLog('에러 렌더링 완료', { message });
}
// 로그 확인 함수
showDebugLogs() {
const statusInfo = [
`📊 총 어록 수: ${this.sayings ? this.sayings.length : 0}`,
`🔍 필터된 어록 수: ${document.querySelectorAll('.saying-item').length}`,
`🎯 현재 카테고리 필터: ${document.getElementById('categoryFilter').value || '전체'}`,
`🏷️ 현재 타겟 필터: ${document.getElementById('targetFilter').value || '전체'}`,
`👤 현재 사용자: ${this.currentUser ? this.currentUser.email : '로그인 필요'}`,
`🔔 백그라운드 새 어록 감지: 활성화 (1분 간격)`,
`⏰ 백그라운드 마지막 확인: 백그라운드 스크립트에서 관리`,
`📱 브라우저 알림: 활성화`,
`💾 로컬 스토리지 상태: ${Object.keys(localStorage).length}개 항목`
].join('\n');
const userConfirmed = confirm(`🛠️ 어록 관리 시스템 상태\n\n${statusInfo}\n\n새 어록 감지 기능을 테스트하시겠습니까?`);
if (userConfirmed) {
this.checkBackgroundNewSayings();
}
}
// 현재 사용자 정보 로드
async loadCurrentUser() {
try {
this.debugLog('현재 사용자 정보 로드 시작');
// 현재 사용자 정보 가져오기
const authRes = await fetch(`${this.SUPABASE_URL}/auth/v1/user`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${this.ACCESS_TOKEN}`,
'apikey': this.SUPABASE_ANON_KEY,
'Content-Type': 'application/json',
'Accept': 'application/json'
},
mode: 'cors',
credentials: 'omit'
});
if (!authRes.ok) {
throw new Error('사용자 인증 실패');
}
const authUser = await authRes.json();
// 사용자 닉네임 가져오기
const userRes = await fetch(`${this.SUPABASE_URL}/rest/v1/users?select=id,nickname&email=eq.${encodeURIComponent(authUser.email)}&limit=1`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${this.ACCESS_TOKEN}`,
'apikey': this.SUPABASE_ANON_KEY,
'Content-Type': 'application/json',
'Accept': 'application/json'
},
mode: 'cors',
credentials: 'omit'
});
if (!userRes.ok) {
throw new Error('사용자 정보 조회 실패');
}
const userData = await userRes.json();
if (!userData || userData.length === 0) {
throw new Error('사용자 데이터를 찾을 수 없습니다');
}
this.currentUser = {
id: userData[0].id,
nickname: userData[0].nickname || authUser.email,
email: authUser.email
};
this.debugLog('현재 사용자 정보 로드 완료', this.currentUser);
// UI에 사용자 정보 표시 (헤더)
const currentUserElement = document.getElementById('current-user');
if (currentUserElement) {
currentUserElement.innerHTML = `👤 ${this.currentUser.nickname}`;
}
// UI에 사용자 정보 표시 (모달)
const modalCurrentUserElement = document.getElementById('modal-current-user');
if (modalCurrentUserElement) {
modalCurrentUserElement.innerHTML = `👤 ${this.currentUser.nickname}`;
modalCurrentUserElement.style.color = '#2c3e50';
modalCurrentUserElement.style.fontWeight = 'bold';
}
} catch (error) {
this.debugLog('현재 사용자 정보 로드 실패', { error: error.message });
// 에러 상태 표시
const currentUserElement = document.getElementById('current-user');
if (currentUserElement) {
currentUserElement.innerHTML = '👤 사용자 정보 로드 실패';
currentUserElement.style.color = '#e74c3c';
}
const modalCurrentUserElement = document.getElementById('modal-current-user');
if (modalCurrentUserElement) {
modalCurrentUserElement.innerHTML = '❌ 사용자 정보를 불러올 수 없습니다';
modalCurrentUserElement.style.color = '#e74c3c';
}
throw error;
}
}
// 어록 등록 모달 이벤트 등록
attachAddSayingEvents() {
this.debugLog('어록 등록 모달 이벤트 등록 시작');
const addBtn = document.getElementById('add-saying-btn');
const modal = document.getElementById('add-saying-modal');
const closeBtn = document.getElementById('modal-close');
const cancelBtn = document.getElementById('cancel-btn');
const submitBtn = document.getElementById('submit-btn');
const form = document.getElementById('saying-form');
// 모달 열기
if (addBtn) {
addBtn.addEventListener('click', () => {
this.debugLog('어록 등록 모달 열기');
modal.style.display = 'flex';
document.getElementById('saying-title').focus();
});
}
// 모달 닫기
const closeModal = () => {
this.debugLog('어록 등록 모달 닫기');
modal.style.display = 'none';
form.reset();
this.resetPreviewTabs();
};
if (closeBtn) {
closeBtn.addEventListener('click', closeModal);
}
if (cancelBtn) {
cancelBtn.addEventListener('click', closeModal);
}
// 모달 외부 클릭 시 닫기
modal.addEventListener('click', (e) => {
if (e.target === modal) {
closeModal();
}
});
// ESC 키로 모달 닫기
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && modal.style.display === 'flex') {
closeModal();
}
});
// 마크다운 미리보기 탭 이벤트
this.attachPreviewTabEvents();
// 폼 제출 이벤트
if (form) {
form.addEventListener('submit', (e) => {
e.preventDefault();
this.handleSayingSubmit();
});
}
if (submitBtn) {
submitBtn.addEventListener('click', (e) => {
e.preventDefault();
this.handleSayingSubmit();
});
}
this.debugLog('어록 등록 모달 이벤트 등록 완료');
}
// 마크다운 미리보기 탭 이벤트 등록
attachPreviewTabEvents() {
const previewTabs = document.querySelectorAll('.preview-tab');
const contentTextarea = document.getElementById('saying-content');
const previewDiv = document.getElementById('markdown-preview');
previewTabs.forEach(tab => {
tab.addEventListener('click', () => {
// 모든 탭에서 active 클래스 제거
previewTabs.forEach(t => t.classList.remove('active'));
// 클릭된 탭에 active 클래스 추가
tab.classList.add('active');
const tabType = tab.getAttribute('data-tab');
if (tabType === 'edit') {
// 편집 모드
contentTextarea.style.display = 'block';
previewDiv.style.display = 'none';
} else if (tabType === 'preview') {
// 미리보기 모드
contentTextarea.style.display = 'none';
previewDiv.style.display = 'block';
// 마크다운 렌더링
this.renderMarkdownPreview();
}
});
});
// 실시간 미리보기 업데이트
contentTextarea.addEventListener('input', () => {
const activeTab = document.querySelector('.preview-tab.active');
if (activeTab && activeTab.getAttribute('data-tab') === 'preview') {
// 미리보기 탭이 활성화된 경우 실시간 업데이트
this.renderMarkdownPreview();
}
});
}
// 마크다운 미리보기 렌더링 (비동기)
async renderMarkdownPreview() {
const contentTextarea = document.getElementById('saying-content');
const previewDiv = document.getElementById('markdown-preview');
if (!contentTextarea || !previewDiv) {
console.error('❌ 미리보기 요소를 찾을 수 없습니다');
return;
}
const content = contentTextarea.value.trim();
if (!content) {
previewDiv.innerHTML = '<div style="padding: 20px; text-align: center; color: #999;">미리보기할 내용을 입력해주세요</div>';
return;
}
// 로딩 상태 표시
previewDiv.innerHTML = '<div style="padding: 20px; text-align: center; color: #666;">미리보기 생성 중...</div>';
try {
// 마크다운을 HTML로 변환
const htmlContent = await this.convertMarkdownToHtml(content);
// 변환된 HTML을 미리보기에 표시
previewDiv.innerHTML = htmlContent || '<div style="padding: 20px; text-align: center; color: #999;">내용을 변환할 수 없습니다</div>';
console.log('✅ 마크다운 미리보기 렌더링 완료');
} catch (error) {
console.error('❌ 마크다운 미리보기 렌더링 실패:', error);
previewDiv.innerHTML = '<div style="padding: 20px; text-align: center; color: #f44336;">미리보기 생성 중 오류가 발생했습니다</div>';
}
}
// 어록 제출 처리
async handleSayingSubmit() {
this.debugLog('어록 제출 처리 시작');
const title = document.getElementById('saying-title').value.trim();
const content = document.getElementById('saying-content').value.trim();
const category = document.getElementById('saying-category').value;
const target = document.getElementById('saying-target').value;
const submitBtn = document.getElementById('submit-btn');
// 유효성 검사
if (!title) {
alert('❌ 어록 제목을 입력해주세요.');
document.getElementById('saying-title').focus();
return;
}
if (!content) {
alert('❌ 어록 내용을 입력해주세요.');
document.getElementById('saying-content').focus();
return;
}
if (!category) {
alert('❌ 카테고리를 선택해주세요.');
document.getElementById('saying-category').focus();
return;
}
if (!target) {
alert('❌ 타겟을 선택해주세요.');
document.getElementById('saying-target').focus();
return;
}
if (!this.currentUser) {
alert('❌ 사용자 정보를 불러올 수 없습니다. 페이지를 새로고침해주세요.');
return;
}
try {
// 버튼 상태 변경
const originalText = submitBtn.textContent;
submitBtn.textContent = '🔄 등록 중...';
submitBtn.disabled = true;
this.debugLog('어록 등록 API 호출', {
title,
contentLength: content.length,
category,
target,
userId: this.currentUser.id,
userNickname: this.currentUser.nickname
});
// 어록 데이터 생성 (register 필드 사용)
const sayingData = {
saying_title: title,
saying: content,
cat: category,
target: target,
register: this.currentUser.nickname, // 등록자 필드명 변경
user_id: this.currentUser.id, // RLS 정책을 위한 사용자 ID 추가
admin_approval: false, // 기본적으로 승인 대기 상태
created_at: new Date().toISOString()
};
// API 호출
const response = await fetch(`${this.SUPABASE_URL}/rest/v1/tanya_sayings`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.ACCESS_TOKEN}`,
'apikey': this.SUPABASE_ANON_KEY,
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(sayingData)
});
this.debugLog('어록 등록 API 응답', {
status: response.status,
statusText: response.statusText,
ok: response.ok
});
if (!response.ok) {
const errorDetail = await response.text();
this.debugLog('어록 등록 실패', {
status: response.status,
error: errorDetail
});
throw new Error(`어록 등록 실패 (${response.status}: ${response.statusText})\n응답: ${errorDetail}`);
}
this.debugLog('어록 등록 성공');
// 성공 팝업 표시
this.showSuccessPopup(title, category, target);
// 모달 닫기
document.getElementById('add-saying-modal').style.display = 'none';
document.getElementById('saying-form').reset();
this.resetPreviewTabs();
// 어록 목록 새로고침 (승인된 어록만 표시되므로 새로 등록한 어록은 보이지 않음)
await this.loadSayings();
} catch (error) {
this.debugLog('어록 등록 실패', { error: error.message });
alert(`❌ 어록 등록 중 오류가 발생했습니다:\n${error.message}`);
} finally {
// 버튼 상태 복원
submitBtn.textContent = '📝 등록하기';
submitBtn.disabled = false;
}
}
// 성공 팝업 표시
showSuccessPopup(title, category, target) {
const popup = document.createElement('div');
popup.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
z-index: 2000;
text-align: center;
min-width: 400px;
border: 3px solid #28a745;
`;
popup.innerHTML = `
<div style="font-size: 48px; margin-bottom: 16px;">🎉</div>
<h3 style="margin: 0 0 16px 0; color: #28a745;">어록 등록 완료!</h3>
<div style="margin-bottom: 20px; color: #666;">
<div style="margin-bottom: 8px;"><strong>제목:</strong> ${title}</div>
<div style="margin-bottom: 8px;"><strong>카테고리:</strong> ${category}</div>
<div style="margin-bottom: 8px;"><strong>타겟:</strong> ${target}</div>
<div style="margin-bottom: 8px;"><strong>등록자:</strong> ${this.currentUser.nickname}</div>
</div>
<div style="background: #fff3cd; border: 1px solid #ffeaa7; border-radius: 4px; padding: 12px; margin-bottom: 20px; color: #856404;">
<strong>📋 안내사항</strong><br>
등록된 어록은 관리자 승인 후 목록에 표시됩니다.
</div>
<button id="close-popup" style="
padding: 10px 20px;
background: #28a745;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
">확인</button>
`;
// 팝업을 body에 추가
document.body.appendChild(popup);
// 확인 버튼 이벤트
const closeBtn = popup.querySelector('#close-popup');
closeBtn.addEventListener('click', () => {
document.body.removeChild(popup);
});
// 3초 후 자동으로 닫기
setTimeout(() => {
if (document.body.contains(popup)) {
document.body.removeChild(popup);
}
}, 5000);
this.debugLog('성공 팝업 표시 완료', { title, category, target });
}
// 카테고리와 타겟 옵션을 백엔드에서 로드
async loadCategoryAndTargetOptions() {
this.debugLog('카테고리와 타겟 옵션 로드 시작');
try {
// 카테고리 데이터 가져오기
this.debugLog('카테고리 데이터 요청 중...');
const categoryResponse = await fetch(`${this.SUPABASE_URL}/rest/v1/sayings_cat?select=saying_cat&order=saying_cat.asc`, {
headers: {
'apikey': this.SUPABASE_ANON_KEY,
'Authorization': `Bearer ${this.SUPABASE_ANON_KEY}`,
'Content-Type': 'application/json'
}
});
// 타겟 데이터 가져오기
this.debugLog('타겟 데이터 요청 중...');
const targetResponse = await fetch(`${this.SUPABASE_URL}/rest/v1/sayings_target?select=target&order=target.asc`, {
headers: {
'apikey': this.SUPABASE_ANON_KEY,
'Authorization': `Bearer ${this.SUPABASE_ANON_KEY}`,
'Content-Type': 'application/json'
}
});
// 카테고리 응답 처리
if (categoryResponse.ok) {
const categoryData = await categoryResponse.json();
this.debugLog('카테고리 데이터 로드 성공', { count: categoryData.length });
if (categoryData.length === 0) {
throw new Error('카테고리 데이터가 비어있습니다. sayings_cat 테이블을 확인해주세요.');
}
// 카테고리 드롭다운 업데이트
this.updateCategoryDropdowns(categoryData);
} else {
const errorText = await categoryResponse.text();
this.debugLog('카테고리 데이터 로드 실패', {
status: categoryResponse.status,
statusText: categoryResponse.statusText,
error: errorText
});
throw new Error(`카테고리 데이터 로드 실패: ${categoryResponse.status} ${categoryResponse.statusText}`);
}
// 타겟 응답 처리
if (targetResponse.ok) {
const targetData = await targetResponse.json();
this.debugLog('타겟 데이터 로드 성공', { count: targetData.length });
if (targetData.length === 0) {
throw new Error('타겟 데이터가 비어있습니다. sayings_target 테이블을 확인해주세요.');
}
// 타겟 드롭다운 업데이트
this.updateTargetDropdowns(targetData);
} else {
const errorText = await targetResponse.text();
this.debugLog('타겟 데이터 로드 실패', {
status: targetResponse.status,
statusText: targetResponse.statusText,
error: errorText
});
throw new Error(`타겟 데이터 로드 실패: ${targetResponse.status} ${targetResponse.statusText}`);
}
this.debugLog('카테고리와 타겟 옵션 로드 완료');
} catch (error) {
this.debugLog('카테고리/타겟 옵션 로드 중 오류 발생', { error: error.message });
// 에러를 다시 던져서 앱 초기화를 중단
throw new Error(`백엔드에서 드롭다운 데이터를 가져올 수 없습니다: ${error.message}`);
}
}
// 카테고리 드롭다운 업데이트
updateCategoryDropdowns(categoryData) {
const categorySelect = document.getElementById('saying-category');
if (!categorySelect) return;
// 기존 옵션 제거 (첫 번째 "선택하세요" 옵션 제외)
while (categorySelect.children.length > 1) {
categorySelect.removeChild(categorySelect.lastChild);
}
// 새 카테고리 옵션 추가
categoryData.forEach(item => {
const option = document.createElement('option');
option.value = item.saying_cat;
option.textContent = item.saying_cat;
categorySelect.appendChild(option);
});
// "아무거나" 기본값 설정
const defaultCategory = categoryData.find(item => item.saying_cat === '아무거나');
if (defaultCategory) {
categorySelect.value = defaultCategory.saying_cat;
this.debugLog('카테고리 기본값 설정', { 기본값: defaultCategory.saying_cat });
}
// 필터 드롭다운도 업데이트
this.updateFilterCategoryOptions(categoryData);
this.debugLog('카테고리 드롭다운 업데이트 완료', {
옵션개수: categorySelect.children.length - 1,
기본값: categorySelect.value || '없음'
});
}
// 타겟 드롭다운 업데이트
updateTargetDropdowns(targetData) {
const targetSelect = document.getElementById('saying-target');
if (!targetSelect) return;
// 기존 옵션 제거 (첫 번째 "선택하세요" 옵션 제외)
while (targetSelect.children.length > 1) {
targetSelect.removeChild(targetSelect.lastChild);
}
// 새 타겟 옵션 추가
targetData.forEach(item => {
const option = document.createElement('option');
option.value = item.target;
option.textContent = item.target;
targetSelect.appendChild(option);
});
// "누구나" 기본값 설정
const defaultTarget = targetData.find(item => item.target === '누구나');
if (defaultTarget) {
targetSelect.value = defaultTarget.target;
this.debugLog('타겟 기본값 설정', { 기본값: defaultTarget.target });
}
// 필터 드롭다운도 업데이트
this.updateFilterTargetOptions(targetData);
this.debugLog('타겟 드롭다운 업데이트 완료', {
옵션개수: targetSelect.children.length - 1,
기본값: targetSelect.value || '없음'
});
}
// 필터용 카테고리 옵션 업데이트
updateFilterCategoryOptions(categoryData) {
const categoryFilter = document.getElementById('category-filter');
if (!categoryFilter) return;
// 기존 옵션 제거 (첫 번째 "모든 카테고리" 옵션 제외)
while (categoryFilter.children.length > 1) {
categoryFilter.removeChild(categoryFilter.lastChild);
}
// 새 카테고리 옵션 추가
categoryData.forEach(item => {
const option = document.createElement('option');
option.value = item.saying_cat;
option.textContent = item.saying_cat;
categoryFilter.appendChild(option);
});
this.debugLog('필터용 카테고리 옵션 업데이트 완료', {
옵션개수: categoryFilter.children.length - 1
});
}
// 필터용 타겟 옵션 업데이트
updateFilterTargetOptions(targetData) {
const targetFilter = document.getElementById('target-filter');
if (!targetFilter) return;
// 기존 옵션 제거 (첫 번째 "모든 타겟" 옵션 제외)
while (targetFilter.children.length > 1) {
targetFilter.removeChild(targetFilter.lastChild);
}
// 새 타겟 옵션 추가
targetData.forEach(item => {
const option = document.createElement('option');
option.value = item.target;
option.textContent = item.target;
targetFilter.appendChild(option);
});
this.debugLog('필터용 타겟 옵션 업데이트 완료', {
옵션개수: targetFilter.children.length - 1
});
}
// 통계 계산
calculateStats() {
const stats = {
total: this.filteredSayings.length,
byCategory: {},
byTarget: {}
};
this.filteredSayings.forEach(saying => {
// 카테고리별 통계
const category = saying.cat || '기타';
stats.byCategory[category] = (stats.byCategory[category] || 0) + 1;
// 타겟별 통계
const target = saying.target || '기타';
stats.byTarget[target] = (stats.byTarget[target] || 0) + 1;
});
return stats;
}
// 필터 적용
async applyFilters() {
this.debugLog('필터 적용 시작');
const categoryFilter = document.getElementById('category-filter').value;
const targetFilter = document.getElementById('target-filter').value;
const dateFilter = document.getElementById('date-filter').value;
const searchText = document.getElementById('search-input').value.toLowerCase().trim();
this.debugLog('필터 조건', {
category: categoryFilter,
target: targetFilter,
date: dateFilter,
search: searchText
});
this.filteredSayings = this.allSayings.filter(saying => {
// 카테고리 필터
if (categoryFilter && categoryFilter !== 'all') {
if (saying.cat !== categoryFilter) return false;
}
// 타겟 필터
if (targetFilter && targetFilter !== 'all') {
if (saying.target !== targetFilter) return false;
}
// 날짜 필터
if (dateFilter && dateFilter !== 'all') {
const sayingDate = new Date(saying.created_at);
const now = new Date();
switch (dateFilter) {
case 'today':
if (sayingDate.toDateString() !== now.toDateString()) return false;
break;
case 'week':
const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
if (sayingDate < weekAgo) return false;
break;
case 'month':
const monthAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
if (sayingDate < monthAgo) return false;
break;
}
}
// 검색 텍스트 필터
if (searchText) {
const searchableText = [
saying.saying_title || '',
saying.saying || '',
saying.register || saying.user_name || '',
saying.cat || '',
saying.target || ''
].join(' ').toLowerCase();
if (!searchableText.includes(searchText)) return false;
}
return true;
});
this.debugLog('필터 적용 완료', {
원본개수: this.allSayings.length,
필터링된개수: this.filteredSayings.length
});
await this.displaySayings();
}
// 필터 이벤트 연결
attachFilterEvents() {
this.debugLog('필터 이벤트 연결 시작');
// 카테고리 필터
const categoryFilter = document.getElementById('category-filter');
if (categoryFilter) {
categoryFilter.addEventListener('change', async () => await this.applyFilters());
}
// 타겟 필터
const targetFilter = document.getElementById('target-filter');
if (targetFilter) {
targetFilter.addEventListener('change', async () => await this.applyFilters());
}
// 날짜 필터
const dateFilter = document.getElementById('date-filter');
if (dateFilter) {
dateFilter.addEventListener('change', async () => await this.applyFilters());
}
// 검색 입력
const searchInput = document.getElementById('search-input');
if (searchInput) {
searchInput.addEventListener('input', () => {
clearTimeout(this.searchTimeout);
this.searchTimeout = setTimeout(async () => await this.applyFilters(), 300);
});
}
// 필터 초기화 버튼
const resetButton = document.getElementById('reset-filters');
if (resetButton) {
resetButton.addEventListener('click', async () => await this.resetFilters());
}
this.debugLog('필터 이벤트 연결 완료');
}
// 필터 초기화
async resetFilters() {
this.debugLog('필터 초기화 시작');
// 모든 필터 초기화
const categoryFilter = document.getElementById('category-filter');
const targetFilter = document.getElementById('target-filter');
const dateFilter = document.getElementById('date-filter');
const searchInput = document.getElementById('search-input');
if (categoryFilter) categoryFilter.value = 'all';
if (targetFilter) targetFilter.value = 'all';
if (dateFilter) dateFilter.value = 'all';
if (searchInput) searchInput.value = '';
// 필터 적용
await this.applyFilters();
this.debugLog('필터 초기화 완료');
}
// 미리보기 탭 초기화
resetPreviewTabs() {
const previewTabs = document.querySelectorAll('.preview-tab');
const contentTextarea = document.getElementById('saying-content');
const previewDiv = document.getElementById('markdown-preview');
// 모든 탭에서 active 클래스 제거
previewTabs.forEach(tab => tab.classList.remove('active'));
// 편집 탭을 활성화
const editTab = document.querySelector('.preview-tab[data-tab="edit"]');
if (editTab) {
editTab.classList.add('active');
}
// 편집 모드로 전환
if (contentTextarea) {
contentTextarea.style.display = 'block';
}
if (previewDiv) {
previewDiv.style.display = 'none';
previewDiv.innerHTML = '';
}
this.debugLog('미리보기 탭 초기화 완료');
}
// 새 어록 감지 시작 (기존 코드 제거하고 백그라운드 연동 방식으로 교체)
startNewSayingsMonitoring() {
this.debugLog('새 어록 감지 시작');
// 기존 인터벌이 있으면 정리
if (this.newSayingsCheckInterval) {
clearInterval(this.newSayingsCheckInterval);
}
// 새 어록 감지 시작 (백그라운드 연동)
this.startNewSayingsDetection();
}
// 새 어록 감지 체크 (제거됨 - 백그라운드에서 처리)
// async checkForNewSayings() { ... } - 이 함수는 제거됨
// 새 어록 모달 표시 (카운트다운 포함)
showNewSayingsModal(newSayings) {
console.log(`[SayingsManager] 새 어록 모달 표시: ${newSayings.length}`);
// 기존 모달이 있으면 제거
const existingModal = document.getElementById('newSayingsModal');
if (existingModal) {
existingModal.remove();
}
// 모달 HTML 생성
const modalHtml = `
<div id="newSayingsModal" class="modal-overlay" style="
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 10000;
animation: fadeIn 0.3s ease-out;
">
<div class="modal-content" style="
background: white;
border-radius: 12px;
padding: 24px;
max-width: 600px;
max-height: 80vh;
overflow-y: auto;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
animation: slideIn 0.3s ease-out;
position: relative;
">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h2 style="margin: 0; color: #2c3e50; font-size: 24px;">🎉 새 어록 알림</h2>
<div id="countdownTimer" style="
background: #e74c3c;
color: white;
padding: 8px 12px;
border-radius: 20px;
font-weight: bold;
font-size: 14px;
">10초 후 자동 닫힘</div>
</div>
<p style="margin-bottom: 20px; color: #34495e; font-size: 16px;">
<strong>${newSayings.length}개</strong>의 새로운 어록이 등록되었습니다!
</p>
<div id="newSayingsList" style="margin-bottom: 20px;">
${newSayings.map(saying => `
<div style="
border: 1px solid #ecf0f1;
border-radius: 8px;
padding: 16px;
margin-bottom: 12px;
background: #f8f9fa;
">
<div style="font-weight: bold; color: #2c3e50; margin-bottom: 8px;">
"${saying.title || saying.saying_title}"
</div>
<div style="color: #7f8c8d; font-size: 14px; margin-bottom: 4px;">
👤 ${saying.author || saying.register || '알 수 없음'} | 📂 ${saying.sayings_cat?.name || saying.cat || '미분류'} | 🎯 ${saying.sayings_target?.name || saying.target || '전체'}
</div>
<div style="color: #95a5a6; font-size: 12px;">
📅 ${new Date(saying.created_at).toLocaleString('ko-KR')}
</div>
</div>
`).join('')}
</div>
<div style="display: flex; gap: 12px; justify-content: flex-end;">
<button id="dismissNewSayings" style="
padding: 10px 20px;
border: 1px solid #bdc3c7;
background: white;
color: #7f8c8d;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
">나중에</button>
<button id="viewNewSayings" style="
padding: 10px 20px;
border: none;
background: #3498db;
color: white;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
">새 어록 보기</button>
</div>
</div>
</div>
<style>
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slideIn {
from { transform: translateY(-30px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
#newSayingsModal button:hover {
opacity: 0.8;
transform: translateY(-1px);
}
</style>
`;
// 모달을 body에 추가
document.body.insertAdjacentHTML('beforeend', modalHtml);
// 카운트다운 시작
let countdown = 10;
const countdownElement = document.getElementById('countdownTimer');
const countdownInterval = setInterval(() => {
countdown--;
if (countdown > 0) {
countdownElement.textContent = `${countdown}초 후 자동 닫힘`;
} else {
clearInterval(countdownInterval);
this.closeNewSayingsModal();
}
}, 1000);
// 이벤트 리스너 추가
const modal = document.getElementById('newSayingsModal');
const dismissBtn = document.getElementById('dismissNewSayings');
const viewBtn = document.getElementById('viewNewSayings');
// 오버레이 클릭으로 닫기
modal.addEventListener('click', (e) => {
if (e.target === modal) {
clearInterval(countdownInterval);
this.closeNewSayingsModal();
}
});
// 나중에 버튼
dismissBtn.addEventListener('click', () => {
clearInterval(countdownInterval);
this.closeNewSayingsModal();
});
// 새 어록 보기 버튼
viewBtn.addEventListener('click', () => {
clearInterval(countdownInterval);
this.closeNewSayingsModal();
this.loadSayings(); // 어록 목록 새로고침
});
// 모달 참조 저장 (카운트다운 정리용)
this.currentModalCountdown = countdownInterval;
}
// 새 어록 모달 닫기
closeNewSayingsModal() {
const modal = document.getElementById('newSayingsModal');
if (modal) {
modal.style.animation = 'fadeOut 0.3s ease-out';
setTimeout(() => {
modal.remove();
}, 300);
}
// 카운트다운 정리
if (this.currentModalCountdown) {
clearInterval(this.currentModalCountdown);
this.currentModalCountdown = null;
}
}
// 새 어록 감지 정리
cleanup() {
console.log('[SayingsManager] 새 어록 감지 정리');
if (this.newSayingsCheckInterval) {
clearInterval(this.newSayingsCheckInterval);
this.newSayingsCheckInterval = null;
}
if (this.currentModalCountdown) {
clearInterval(this.currentModalCountdown);
this.currentModalCountdown = null;
}
// 모달 제거
const modal = document.getElementById('newSayingsModal');
if (modal) {
modal.remove();
}
}
// 새 어록 감지 시작
startNewSayingsDetection() {
console.log('[SayingsManager] 새 어록 감지 시작');
// 기존 타이머가 있으면 정리
if (this.newSayingsTimer) {
clearInterval(this.newSayingsTimer);
}
// 페이지 로드 시 백그라운드에서 감지된 새 어록 확인
this.checkBackgroundNewSayings();
// 1분마다 새 어록 확인 (백그라운드와 동기화)
this.newSayingsTimer = setInterval(() => {
this.checkBackgroundNewSayings();
}, 60000); // 1분
console.log('[SayingsManager] 새 어록 감지 타이머 설정 완료 (1분 간격)');
}
// 백그라운드에서 감지된 새 어록 확인
async checkBackgroundNewSayings() {
try {
const { hasNewSayings, pendingNewSayings } = await new Promise((resolve) => {
chrome.storage.local.get(['hasNewSayings', 'pendingNewSayings'], resolve);
});
if (hasNewSayings && pendingNewSayings && pendingNewSayings.length > 0) {
console.log(`[SayingsManager] 백그라운드에서 감지된 새 어록: ${pendingNewSayings.length}`);
this.showNewSayingsModal(pendingNewSayings);
// 표시 후 플래그 제거
chrome.storage.local.remove(['hasNewSayings', 'pendingNewSayings']);
}
} catch (error) {
console.error('[SayingsManager] 백그라운드 새 어록 확인 오류:', error);
}
}
}
// 페이지 로드 시 초기화
document.addEventListener('DOMContentLoaded', async () => {
console.log('=== 어록 관리 페이지 초기화 시작 ===');
console.log('현재 시간:', new Date().toLocaleString());
// chrome.storage에서 설정 확인
let debugMode = false; // 기본값
try {
const configData = await chrome.storage.local.get('sayings_config');
console.log('chrome.storage 설정 확인:', {
hasSayingsConfig: !!configData.sayings_config,
config: configData.sayings_config ? {
hasUrl: !!configData.sayings_config.SUPABASE_URL,
hasKey: !!configData.sayings_config.SUPABASE_ANON_KEY,
hasToken: !!configData.sayings_config.ACCESS_TOKEN,
debugMode: configData.sayings_config.DEBUG_MODE,
timestamp: configData.sayings_config.timestamp
} : null
});
// DEBUG_MODE 설정 확인
if (configData.sayings_config && configData.sayings_config.DEBUG_MODE !== undefined) {
debugMode = configData.sayings_config.DEBUG_MODE;
}
} catch (error) {
console.error('chrome.storage 설정 확인 실패:', error);
}
// 디버그 요소 표시/숨김 처리
const debugElement = document.getElementById('debug-info');
if (debugElement) {
if (debugMode) {
debugElement.style.display = "block";
console.log('디버그 모드 활성화 - 디버그 정보 표시');
} else {
debugElement.style.display = "none";
console.log('디버그 모드 비활성화 - 디버그 정보 숨김');
}
}
// 로그 확인 버튼 이벤트 등록 (DEBUG_MODE일 때만)
const logCheckBtn = document.getElementById('log-check-btn');
if (logCheckBtn) {
if (debugMode) {
console.log('디버그 모드: 로그 확인 버튼 이벤트 등록');
logCheckBtn.style.display = "inline-block";
logCheckBtn.addEventListener('click', async (e) => {
e.preventDefault();
console.log('로그 확인 버튼 클릭됨');
// 버튼 상태 변경
const originalText = logCheckBtn.textContent;
logCheckBtn.textContent = '🔄 확인 중...';
logCheckBtn.disabled = true;
try {
// SayingsManager가 초기화되었는지 확인
if (window.sayingsManager && typeof window.sayingsManager.showDebugLogs === 'function') {
await window.sayingsManager.showDebugLogs();
} else {
console.error('SayingsManager가 초기화되지 않았습니다');
alert('❌ 관리자가 초기화되지 않았습니다. 페이지를 새로고침해주세요.');
}
} finally {
// 버튼 상태 복원
logCheckBtn.textContent = originalText;
logCheckBtn.disabled = false;
}
});
console.log('로그 확인 버튼 이벤트 등록 완료');
} else {
console.log('디버그 모드 비활성화: 로그 확인 버튼 숨김');
logCheckBtn.style.display = "none";
}
}
try {
// SayingsManager 초기화
console.log('SayingsManager 인스턴스 생성 중...');
window.sayingsManager = new SayingsManager();
console.log('SayingsManager 초기화 시작...');
await window.sayingsManager.initialize();
console.log('✅ 어록 관리 페이지 초기화 완료');
} catch (error) {
console.error('❌ 초기화 실패:', error);
// UI에 오류 표시
const statsElement = document.getElementById('sayings-stats');
if (statsElement) {
statsElement.innerHTML = `
<div style="color: red; text-align: center; padding: 20px;">
❌ 초기화 실패<br>
${error.message}<br>
<small>로그인 후 다시 시도해주세요.</small>
</div>
`;
}
const container = document.getElementById("sayings-container");
if (container) {
container.innerHTML = `
<div class="empty-state">
<div class="icon">❌</div>
<div class="title">초기화 실패</div>
<div class="description">${error.message}</div>
</div>
`;
}
// 디버그 정보에도 표시 (DEBUG_MODE일 때만)
if (debugMode) {
const debugElement = document.getElementById('debug-info');
if (debugElement) {
debugElement.innerHTML = `❌ 초기화 실패: ${error.message}<br><button id="log-check-btn" style="display: inline-block;">📋 로그 확인</button>`;
debugElement.style.display = "block";
// 버튼 이벤트 다시 등록
const newBtn = document.getElementById('log-check-btn');
if (newBtn) {
newBtn.addEventListener('click', async (e) => {
e.preventDefault();
// 간단한 로그 정보 표시
const logInfo = `❌ 초기화 실패 상태\n\n오류: ${error.message}\n\n시간: ${new Date().toLocaleString()}`;
alert(logInfo);
});
}
}
}
}
console.log('=== DOMContentLoaded 이벤트 처리 완료 ===');
});
// 페이지 언로드 시 정리
window.addEventListener('beforeunload', () => {
console.log('페이지 언로드 - 새 어록 감지 기능 정리');
if (window.sayingsManager) {
window.sayingsManager.cleanup();
}
});
// 전역 함수로 등록
window.sayingsManager = null;