fix: address all Obsidian plugin review feedback (8 items)

1. Remove main.js from git repository
2. Update LICENSE year from 2025 to 2026
3. Prefix all generic CSS classes with 'kga-' namespace to avoid
   conflicts with other plugins and Obsidian itself
4. Use registerEvent() for workspace events to ensure proper cleanup
5. Convert commands to conditional commands using checkCallback
6. Remove global variables (window.koreanGrammarPlugin) and use
   callback injection pattern instead
7. Replace deprecated activeLeaf with getActiveViewOfType(MarkdownView)
8. Remove top-level heading from settings tab per Obsidian guidelines

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hyungyunlim 2026-02-13 14:14:11 +09:00
parent b5aad78422
commit 5d7df00af9
37 changed files with 1194 additions and 15657 deletions

View file

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 hyungyunlim
Copyright (c) 2026 hyungyunlim
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

14383
main.js

File diff suppressed because it is too large Load diff

193
main.ts
View file

@ -34,9 +34,7 @@ export default class KoreanGrammarPlugin extends Plugin {
orchestrator: SpellCheckOrchestrator;
// 🤖 InlineModeService는 정적 클래스로 설계되어 인스턴스 불필요
// 🔧 문서 전환 감지 이벤트 참조
private fileOpenListener?: any;
private activeLeafChangeListener?: any;
// 🤖 InlineModeService 참조 (전역 변수 대신 인스턴스 속성 사용)
async onload() {
// 디버그/프로덕션 모드 설정
@ -67,96 +65,92 @@ export default class KoreanGrammarPlugin extends Plugin {
await this.orchestrator.execute();
});
// 명령어 등록
// 명령어 등록 (conditional commands)
this.addCommand({
id: "check-korean-spelling",
name: "한국어 맞춤법 검사",
callback: async () => {
await this.orchestrator.execute();
checkCallback: (checking: boolean) => {
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
if (!view) return false;
if (!checking) this.orchestrator.execute();
return true;
},
});
// 현재 문단 맞춤법 검사 명령어 추가
this.addCommand({
id: "check-current-paragraph",
name: "현재 문단 맞춤법 검사",
callback: async () => {
await this.orchestrator.executeCurrentParagraph();
checkCallback: (checking: boolean) => {
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
if (!view) return false;
if (!checking) this.orchestrator.executeCurrentParagraph();
return true;
},
});
// 현재 단어 맞춤법 검사 명령어 추가
this.addCommand({
id: "check-current-word",
name: "현재 단어 맞춤법 검사",
callback: async () => {
await this.orchestrator.executeCurrentWord();
checkCallback: (checking: boolean) => {
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
if (!view) return false;
if (!checking) this.orchestrator.executeCurrentWord();
return true;
},
});
// 현재 문장 맞춤법 검사 명령어 추가
this.addCommand({
id: "check-current-sentence",
name: "현재 문장 맞춤법 검사",
callback: async () => {
await this.orchestrator.executeCurrentSentence();
checkCallback: (checking: boolean) => {
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
if (!view) return false;
if (!checking) this.orchestrator.executeCurrentSentence();
return true;
},
});
// 인라인 모드 명령어 추가 (베타 기능)
this.addCommand({
id: "inline-spell-check",
name: "인라인 맞춤법 검사 (베타)",
callback: async () => {
if (!this.settings.inlineMode.enabled) {
new Notice("인라인 모드가 비활성화되어 있습니다. 설정에서 베타 기능을 활성화하세요.");
return;
}
await this.executeInlineSpellCheck();
checkCallback: (checking: boolean) => {
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
if (!view || !this.settings.inlineMode.enabled) return false;
if (!checking) this.executeInlineSpellCheck();
return true;
},
});
// 🤖 인라인 모드 AI 분석 명령어 추가
this.addCommand({
id: "inline-ai-analysis",
name: "🤖 인라인 AI 분석 (베타)",
callback: async () => {
if (!this.settings.inlineMode.enabled) {
new Notice("인라인 모드가 비활성화되어 있습니다. 설정에서 베타 기능을 활성화하세요.");
return;
}
if (!this.settings.ai.enabled) {
new Notice("AI 기능이 비활성화되어 있습니다. 설정에서 AI 기능을 활성화하세요.");
return;
}
await this.executeInlineAIAnalysis();
name: "인라인 AI 분석 (베타)",
checkCallback: (checking: boolean) => {
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
if (!view || !this.settings.inlineMode.enabled || !this.settings.ai.enabled) return false;
if (!checking) this.executeInlineAIAnalysis();
return true;
},
});
// 📝 인라인 모드 일괄 적용 명령어 추가
this.addCommand({
id: "inline-apply-all",
name: "📝 인라인 오류 일괄 적용",
callback: async () => {
if (!this.settings.inlineMode.enabled) {
new Notice("인라인 모드가 비활성화되어 있습니다. 설정에서 베타 기능을 활성화하세요.");
return;
}
await this.executeInlineApplyAll();
name: "인라인 오류 일괄 적용",
checkCallback: (checking: boolean) => {
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
if (!view || !this.settings.inlineMode.enabled) return false;
if (!checking) this.executeInlineApplyAll();
return true;
},
});
// 인라인 분석 결과 표시 일괄 취소 명령어 추가
this.addCommand({
id: "inline-clear-all",
name: "🗑️ 인라인 분석 결과 표시 일괄 취소",
callback: async () => {
if (!this.settings.inlineMode.enabled) {
new Notice("인라인 모드가 비활성화되어 있습니다. 설정에서 베타 기능을 활성화하세요.");
return;
}
await this.executeInlineClearAll();
name: "인라인 분석 결과 표시 일괄 취소",
checkCallback: (checking: boolean) => {
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
if (!view || !this.settings.inlineMode.enabled) return false;
if (!checking) this.executeInlineClearAll();
return true;
},
});
@ -168,28 +162,14 @@ export default class KoreanGrammarPlugin extends Plugin {
// 설정 탭 추가
this.addSettingTab(new ModernSettingsTab(this.app, this));
// 🤖 전역 설정 등록 (인라인 모드 AI 분석용)
(window as any).koreanGrammarPlugin = {
settings: this.settings,
instance: this
};
// 🔧 문서 전환 감지 이벤트 리스너 등록
this.setupDocumentChangeListeners();
}
onunload() {
// 🔧 문서 전환 감지 이벤트 리스너 정리
if (this.fileOpenListener) {
this.app.workspace.offref(this.fileOpenListener);
}
if (this.activeLeafChangeListener) {
this.app.workspace.offref(this.activeLeafChangeListener);
}
// 인라인 모드 정리
this.disableInlineMode();
// 오케스트레이터 정리
if (this.orchestrator) {
this.orchestrator.destroy();
@ -213,21 +193,20 @@ export default class KoreanGrammarPlugin extends Plugin {
*
*/
async executeInlineSpellCheck(): Promise<void> {
const activeLeaf = this.app.workspace.activeLeaf;
if (!activeLeaf) {
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
if (!view) {
new Notice("활성화된 편집기가 없습니다.");
return;
}
// @ts-ignore - Obsidian 내부 API 사용
const editor = activeLeaf.view.editor;
const editor = view.editor;
if (!editor) {
new Notice("편집기를 찾을 수 없습니다.");
return;
}
// @ts-ignore - CodeMirror 6 에디터 뷰 접근
const editorView = editor.cm;
const editorView = (editor as any).cm;
if (!editorView) {
new Notice("CodeMirror 에디터 뷰를 찾을 수 없습니다.");
return;
@ -235,7 +214,7 @@ export default class KoreanGrammarPlugin extends Plugin {
try {
// 에디터 뷰 및 설정 초기화
InlineModeService.setEditorView(editorView, this.settings, this.app);
InlineModeService.setEditorView(editorView, this.settings, this.app, async (s) => { this.settings = s; await this.saveSettings(); });
// 선택된 텍스트 확인
const selectedText = editor.getSelection();
@ -312,19 +291,15 @@ export default class KoreanGrammarPlugin extends Plugin {
this.registerEditorExtension([errorDecorationField, temporarySuggestionModeField]);
// InlineModeService 초기화 (키보드 단축키 지원을 위해 필요)
const activeLeaf = this.app.workspace.activeLeaf;
if (activeLeaf && activeLeaf.view && (activeLeaf.view as any).editor) {
// @ts-ignore - Obsidian 내부 API 사용
const editorView = (activeLeaf.view as any).editor.cm;
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
if (activeView?.editor) {
const editorView = (activeView.editor as any).cm;
if (editorView) {
InlineModeService.setEditorView(editorView, this.settings, this.app);
InlineModeService.setEditorView(editorView, this.settings, this.app, async (s) => { this.settings = s; await this.saveSettings(); });
Logger.log('인라인 모드: InlineModeService 키보드 스코프 초기화됨');
}
}
// 전역 접근을 위한 참조 설정
(window as any).InlineModeService = InlineModeService;
Logger.log('인라인 모드 활성화됨 (InlineModeService + 키보드 단축키)');
} catch (error) {
@ -337,11 +312,6 @@ export default class KoreanGrammarPlugin extends Plugin {
*
*/
disableInlineMode(): void {
// 전역 객체 정리
if ((window as any).InlineModeService) {
delete (window as any).InlineModeService;
}
Logger.log('인라인 모드 비활성화됨');
}
@ -393,7 +363,7 @@ export default class KoreanGrammarPlugin extends Plugin {
// 🔥 SMART FIX: 현재 문서 텍스트에 실제로 존재하는 오류만 유지
Logger.log('🔥 SMART FIX: 현재 문서 기준으로 오류 필터링');
InlineModeService.setEditorView(currentEditorView, this.settings, this.app);
InlineModeService.setEditorView(currentEditorView, this.settings, this.app, async (s) => { this.settings = s; await this.saveSettings(); });
InlineModeService.filterErrorsByCurrentDocument(targetText);
// 🔧 이제 정리된 상태에서 현재 문서의 오류 상태 확인
@ -531,8 +501,8 @@ export default class KoreanGrammarPlugin extends Plugin {
const processNotice = new Notice('📝 맞춤법 검사를 시작합니다...', 0);
// 에디터 정보 가져오기
const activeLeaf = this.app.workspace.activeLeaf;
if (!activeLeaf) {
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
if (!activeView) {
processNotice.hide();
new Notice('활성화된 편집기가 없습니다.');
return;
@ -541,12 +511,11 @@ export default class KoreanGrammarPlugin extends Plugin {
try {
// 1단계: 맞춤법 검사 실행
processNotice.setMessage(`📝 ${targetText.length}자 텍스트 맞춤법 검사 중...`);
// 🔧 단일 InlineModeService 시스템 사용
// @ts-ignore - Obsidian 내부 API 사용
const editorView = (activeLeaf.view as any).editor?.cm;
const editorView = (activeView.editor as any)?.cm;
if (editorView) {
InlineModeService.setEditorView(editorView, this.settings, this.app);
InlineModeService.setEditorView(editorView, this.settings, this.app, async (s) => { this.settings = s; await this.saveSettings(); });
}
// InlineModeService를 통한 맞춤법 검사 실행
@ -627,8 +596,8 @@ export default class KoreanGrammarPlugin extends Plugin {
* 📝
*/
async executeInlineApplyAll(): Promise<void> {
const activeLeaf = this.app.workspace.activeLeaf;
if (!activeLeaf || !activeLeaf.view || !(activeLeaf.view as any).editor) {
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
if (!view?.editor) {
new Notice('활성화된 편집기가 없습니다.');
return;
}
@ -644,10 +613,9 @@ export default class KoreanGrammarPlugin extends Plugin {
try {
// 에디터 뷰 설정
// @ts-ignore - Obsidian 내부 API 사용
const editorView = (activeLeaf.view as any).editor?.cm;
const editorView = (view.editor as any)?.cm;
if (editorView) {
InlineModeService.setEditorView(editorView, this.settings, this.app);
InlineModeService.setEditorView(editorView, this.settings, this.app, async (s) => { this.settings = s; await this.saveSettings(); });
}
// 일괄 적용 실행
@ -673,8 +641,8 @@ export default class KoreanGrammarPlugin extends Plugin {
*
*/
async executeInlineClearAll(): Promise<void> {
const activeLeaf = this.app.workspace.activeLeaf;
if (!activeLeaf || !activeLeaf.view || !(activeLeaf.view as any).editor) {
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
if (!view?.editor) {
new Notice('활성화된 편집기가 없습니다.');
return;
}
@ -690,10 +658,9 @@ export default class KoreanGrammarPlugin extends Plugin {
try {
// 에디터 뷰 설정
// @ts-ignore - Obsidian 내부 API 사용
const editorView = (activeLeaf.view as any).editor?.cm;
const editorView = (view.editor as any)?.cm;
if (editorView) {
InlineModeService.setEditorView(editorView, this.settings, this.app);
InlineModeService.setEditorView(editorView, this.settings, this.app, async (s) => { this.settings = s; await this.saveSettings(); });
// 모든 오류 제거
InlineModeService.clearErrors(editorView);
@ -812,34 +779,34 @@ export default class KoreanGrammarPlugin extends Plugin {
private setupDocumentChangeListeners(): void {
// 파일 변경 감지 - 다른 파일로 이동할 때 트리거
this.fileOpenListener = this.app.workspace.on('file-open', (file) => {
this.registerEvent(this.app.workspace.on('file-open', () => {
// 인라인 모드가 활성화되어 있고 오류가 있으면 상태 완전 정리
if (this.settings?.inlineMode?.enabled && InlineModeService.hasErrors()) {
Logger.log('🔧 file-open: 이전 문서의 인라인 오류 상태 완전 정리');
InlineModeService.forceCleanAllErrors();
}
// 새로운 문서에 인라인 모드 설정 (오류 상태는 깨끗한 상태에서 시작)
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
if (activeView?.editor && this.settings?.inlineMode?.enabled) {
// @ts-ignore - Obsidian 내부 API 사용
const currentEditorView = (activeView as any).editor?.cm;
if (currentEditorView) {
InlineModeService.setEditorView(currentEditorView, this.settings, this.app);
InlineModeService.setEditorView(currentEditorView, this.settings, this.app, async (s) => { this.settings = s; await this.saveSettings(); });
}
}
});
}));
// 리프 변경 감지 - 탭 변경, 패널 변경 등을 포함한 더 광범위한 변경 감지
this.activeLeafChangeListener = this.app.workspace.on('active-leaf-change', (leaf) => {
this.registerEvent(this.app.workspace.on('active-leaf-change', (leaf) => {
// 인라인 모드가 활성화되어 있고 오류가 있으면 먼저 상태 정리
if (this.settings?.inlineMode?.enabled && InlineModeService.hasErrors()) {
Logger.log('🔧 active-leaf-change: 이전 탭의 인라인 오류 상태 완전 정리');
InlineModeService.forceCleanAllErrors();
}
// 마크다운 뷰로 변경되었을 때만 새로운 뷰 설정
if (leaf?.view?.getViewType() === 'markdown' && this.settings?.inlineMode?.enabled) {
const markdownView = leaf.view as MarkdownView;
@ -847,11 +814,11 @@ export default class KoreanGrammarPlugin extends Plugin {
// @ts-ignore - Obsidian 내부 API 사용
const currentEditorView = (markdownView as any).editor?.cm;
if (currentEditorView) {
InlineModeService.setEditorView(currentEditorView, this.settings, this.app);
InlineModeService.setEditorView(currentEditorView, this.settings, this.app, async (s) => { this.settings = s; await this.saveSettings(); });
}
}
}
});
}));
}
}

View file

@ -170,22 +170,22 @@ export class TokenCalculator implements IPopupServiceManager {
return new Promise((resolve) => {
// 모달 컨테이너 생성
const modal = document.createElement('div');
modal.className = 'modal-container token-warning-modal';
modal.className = 'modal-container kga-token-warning-modal';
// 모달 콘텐츠
const content = document.createElement('div');
content.className = 'modal token-warning-content';
content.className = 'modal kga-token-warning-content';
// DOM API를 사용한 안전한 모달 생성
const title = content.createDiv({ cls: 'modal-title token-warning-title' });
const title = content.createDiv({ cls: 'modal-title kga-token-warning-title' });
title.textContent = '🚨 토큰 사용량 경고';
const modalContent = content.createDiv({ cls: 'modal-content token-warning-body' });
const modalContent = content.createDiv({ cls: 'modal-content kga-token-warning-body' });
const p1 = modalContent.createEl('p');
p1.textContent = 'AI 분석에 많은 토큰이 사용될 예정입니다:';
const infoBox = modalContent.createDiv({ cls: 'token-warning-info-box' });
const infoBox = modalContent.createDiv({ cls: 'kga-token-warning-info-box' });
const tokenInfo = infoBox.createDiv();
const tokenStrong = tokenInfo.createEl('strong');
@ -198,7 +198,7 @@ export class TokenCalculator implements IPopupServiceManager {
costInfo.appendChild(document.createTextNode(` ${tokenUsage.estimatedCost}`));
if (tokenUsage.morphemeOptimized) {
const optimizedInfo = infoBox.createDiv({ cls: 'token-warning-optimized' });
const optimizedInfo = infoBox.createDiv({ cls: 'kga-token-warning-optimized' });
const optimizedStrong = optimizedInfo.createEl('strong');
optimizedStrong.textContent = '✓ 형태소 최적화 적용됨';
}
@ -206,12 +206,12 @@ export class TokenCalculator implements IPopupServiceManager {
const p2 = modalContent.createEl('p');
p2.textContent = '계속 진행하시겠습니까?';
const buttonContainer = content.createDiv({ cls: 'modal-button-container token-warning-buttons' });
const buttonContainer = content.createDiv({ cls: 'modal-button-container kga-token-warning-buttons' });
const proceedBtn = buttonContainer.createEl('button', { cls: 'mod-cta token-warning-proceed' });
const proceedBtn = buttonContainer.createEl('button', { cls: 'mod-cta kga-token-warning-proceed' });
proceedBtn.textContent = '진행';
const cancelBtn = buttonContainer.createEl('button', { cls: 'token-warning-cancel' });
const cancelBtn = buttonContainer.createEl('button', { cls: 'kga-token-warning-cancel' });
cancelBtn.textContent = '취소';
modal.appendChild(content);
document.body.appendChild(modal);

View file

@ -138,8 +138,8 @@ export class ClickHandler {
// 네비게이션 버튼 클릭
if (target.classList.contains('nav-button') ||
target.classList.contains('pagination-btn') ||
target.closest('.nav-button, .pagination-btn')) {
target.classList.contains('kga-pagination-btn') ||
target.closest('.nav-button, .kga-pagination-btn')) {
return 'navigation';
}
@ -420,7 +420,7 @@ export class ClickHandler {
// 클릭 가능한 클래스들
const clickableClasses = [
'error-text', 'error-highlight', 'suggestion-item',
'nav-button', 'pagination-btn', 'btn', 'toggle-btn',
'nav-button', 'kga-pagination-btn', 'btn', 'toggle-btn',
'edit-btn', 'error-edit-btn'
];

View file

@ -105,7 +105,7 @@ export class HoverHandler {
*/
private createTooltipContainer(): void {
this.tooltipContainer = document.createElement('div');
this.tooltipContainer.className = 'popup-tooltip-container';
this.tooltipContainer.className = 'kga-popup-tooltip-container';
document.body.appendChild(this.tooltipContainer);
}
@ -240,8 +240,8 @@ export class HoverHandler {
// 네비게이션 버튼 호버
if (target.classList.contains('nav-button') ||
target.classList.contains('pagination-btn') ||
target.closest('.nav-button, .pagination-btn')) {
target.classList.contains('kga-pagination-btn') ||
target.closest('.nav-button, .kga-pagination-btn')) {
return 'navigation-hint';
}
@ -300,37 +300,37 @@ export class HoverHandler {
): Promise<TooltipConfig | null> {
let content = '';
let className = 'hover-tooltip';
let className = 'kga-hover-tooltip';
switch (actionType) {
case 'error-preview':
content = await this.createErrorPreviewContent(context);
className = 'error-preview-tooltip';
className = 'kga-error-preview-tooltip';
break;
case 'suggestion-tooltip':
content = await this.createSuggestionTooltipContent(context);
className = 'suggestion-tooltip';
className = 'kga-suggestion-tooltip';
break;
case 'ai-info':
content = await this.createAIInfoContent(context);
className = 'ai-info-tooltip';
className = 'kga-ai-info-tooltip';
break;
case 'help-tooltip':
content = await this.createHelpTooltipContent(context);
className = 'help-tooltip';
className = 'kga-help-tooltip';
break;
case 'button-hint':
content = await this.createButtonHintContent(context);
className = 'button-hint-tooltip';
className = 'kga-button-hint-tooltip';
break;
case 'navigation-hint':
content = await this.createNavigationHintContent(context);
className = 'navigation-hint-tooltip';
className = 'kga-navigation-hint-tooltip';
break;
default:
@ -361,17 +361,17 @@ export class HoverHandler {
if (correctionIndex === undefined) return '';
// 기본 오류 정보
let content = `<div class="error-preview-content">`;
content += `<div class="error-title">맞춤법 오류 #${correctionIndex + 1}</div>`;
let content = `<div class="kga-error-preview-content">`;
content += `<div class="kga-error-title">맞춤법 오류 #${correctionIndex + 1}</div>`;
// 오류 텍스트
const errorText = target.textContent || '';
if (errorText) {
content += `<div class="error-original">원본: "${errorText}"</div>`;
content += `<div class="kga-error-original">원본: "${errorText}"</div>`;
}
// 추가 정보 (Phase 7에서 상태 관리자와 연결 예정)
content += `<div class="error-hint">클릭하여 수정 제안 확인</div>`;
content += `<div class="kga-error-hint">클릭하여 수정 제안 확인</div>`;
content += `</div>`;
return content;
@ -386,9 +386,9 @@ export class HoverHandler {
const suggestionText = target.textContent || '';
if (!suggestionText) return '';
let content = `<div class="suggestion-tooltip-content">`;
content += `<div class="suggestion-text">"${suggestionText}"</div>`;
content += `<div class="suggestion-hint">클릭하여 이 제안 적용</div>`;
let content = `<div class="kga-suggestion-tooltip-content">`;
content += `<div class="kga-suggestion-text">"${suggestionText}"</div>`;
content += `<div class="kga-suggestion-hint">클릭하여 이 제안 적용</div>`;
content += `</div>`;
return content;
@ -404,15 +404,15 @@ export class HoverHandler {
const confidence = target.dataset.confidence || '';
const reasoning = target.dataset.reasoning || '';
let content = `<div class="ai-info-content">`;
content += `<div class="ai-title">🤖 AI 분석 정보</div>`;
let content = `<div class="kga-ai-info-content">`;
content += `<div class="kga-ai-title">🤖 AI 분석 정보</div>`;
if (confidence) {
content += `<div class="ai-confidence">신뢰도: ${confidence}%</div>`;
content += `<div class="kga-ai-confidence">신뢰도: ${confidence}%</div>`;
}
if (reasoning) {
content += `<div class="ai-reasoning">${reasoning}</div>`;
content += `<div class="kga-ai-reasoning">${reasoning}</div>`;
}
content += `</div>`;
@ -429,8 +429,8 @@ export class HoverHandler {
const helpText = target.dataset.help || target.title || '';
if (!helpText) return '';
let content = `<div class="help-tooltip-content">`;
content += `<div class="help-text">${helpText}</div>`;
let content = `<div class="kga-help-tooltip-content">`;
content += `<div class="kga-help-text">${helpText}</div>`;
content += `</div>`;
return content;
@ -445,14 +445,14 @@ export class HoverHandler {
const buttonText = target.textContent || '';
const shortcut = target.dataset.shortcut || '';
let content = `<div class="button-hint-content">`;
let content = `<div class="kga-button-hint-content">`;
if (buttonText) {
content += `<div class="button-name">${buttonText}</div>`;
content += `<div class="kga-button-name">${buttonText}</div>`;
}
if (shortcut) {
content += `<div class="button-shortcut">단축키: ${shortcut}</div>`;
content += `<div class="kga-button-shortcut">단축키: ${shortcut}</div>`;
}
content += `</div>`;
@ -480,8 +480,8 @@ export class HoverHandler {
if (!hintText) return '';
let content = `<div class="navigation-hint-content">`;
content += `<div class="navigation-text">${hintText}</div>`;
let content = `<div class="kga-navigation-hint-content">`;
content += `<div class="kga-navigation-text">${hintText}</div>`;
content += `</div>`;
return content;
@ -496,7 +496,7 @@ export class HoverHandler {
*/
private createTooltipElement(config: TooltipConfig): HTMLElement {
const tooltip = document.createElement('div');
tooltip.className = `popup-tooltip ${config.className || ''} kga-dynamic-position kga-tooltip-enter`;
tooltip.className = `kga-popup-tooltip ${config.className || ''} kga-dynamic-position kga-tooltip-enter`;
// Set position using CSS variables
tooltip.style.setProperty('--kga-pos-left', `${config.position.x}px`);

View file

@ -525,7 +525,7 @@ export class MobileEventHandler {
const input = document.createElement('input');
input.type = 'text';
input.value = target.textContent || '';
input.className = 'error-original-input mobile-edit-input';
input.className = 'kga-error-original-input kga-mobile-edit-input';
// data 속성 복사
if (context.correctionIndex !== undefined) {
@ -538,7 +538,7 @@ export class MobileEventHandler {
// 편집 컨테이너 생성
const editContainer = document.createElement('div');
editContainer.className = 'mobile-edit-container';
editContainer.className = 'kga-mobile-edit-container';
editContainer.appendChild(input);
editContainer.appendChild(buttonContainer);
@ -567,17 +567,17 @@ export class MobileEventHandler {
): HTMLElement {
const container = document.createElement('div');
container.className = 'mobile-edit-buttons';
container.className = 'kga-mobile-edit-buttons';
// 완료 버튼
const confirmBtn = document.createElement('button');
confirmBtn.textContent = '✓';
confirmBtn.className = 'mobile-edit-confirm';
confirmBtn.className = 'kga-mobile-edit-confirm';
// 취소 버튼
const cancelBtn = document.createElement('button');
cancelBtn.textContent = '✕';
cancelBtn.className = 'mobile-edit-cancel';
cancelBtn.className = 'kga-mobile-edit-cancel';
// 이벤트 리스너
confirmBtn.addEventListener('click', (e) => {
@ -658,7 +658,7 @@ export class MobileEventHandler {
if (!input) return;
// 편집 컨테이너 제거
const editContainer = input.closest('.mobile-edit-container');
const editContainer = input.closest('.kga-mobile-edit-container');
if (editContainer) {
editContainer.remove();
}
@ -683,8 +683,8 @@ export class MobileEventHandler {
// 편집 중인 요소나 특정 UI 요소에서는 스크롤 방지
return target.classList.contains('error-text') ||
target.classList.contains('suggestion-item') ||
target.classList.contains('mobile-edit-input') ||
!!target.closest('.error-text, .suggestion-item, .mobile-edit-container');
target.classList.contains('kga-mobile-edit-input') ||
!!target.closest('.error-text, .suggestion-item, .kga-mobile-edit-container');
}
/**
@ -695,7 +695,7 @@ export class MobileEventHandler {
const touchableClasses = [
'error-text', 'error-highlight', 'suggestion-item',
'nav-button', 'pagination-btn', 'btn', 'toggle-btn',
'nav-button', 'kga-pagination-btn', 'btn', 'toggle-btn',
'edit-btn'
];

View file

@ -298,7 +298,7 @@ export class PopupEventManager implements IPopupServiceManager {
}
// 네비게이션 버튼 클릭
this.addEventRule('.nav-button, .pagination-btn', 'click', async (event, context) => {
this.addEventRule('.nav-button, .kga-pagination-btn', 'click', async (event, context) => {
Logger.debug('네비게이션 버튼 클릭됨');
return await this.handleNavigationClick(event, context);
});

View file

@ -30,9 +30,9 @@ export class FocusManager implements IPopupComponent {
private focusObserver?: MutationObserver;
// CSS 클래스 상수
private readonly FOCUS_CLASS = 'keyboard-focused';
private readonly FOCUS_HIGHLIGHT_CLASS = 'focus-highlight';
private readonly EDIT_MODE_CLASS = 'edit-mode-active';
private readonly FOCUS_CLASS = 'kga-keyboard-focused';
private readonly FOCUS_HIGHLIGHT_CLASS = 'kga-focus-highlight';
private readonly EDIT_MODE_CLASS = 'kga-edit-mode-active';
constructor() {
this.focusState = this.createInitialFocusState();
@ -55,7 +55,7 @@ export class FocusManager implements IPopupComponent {
}
render(): HTMLElement {
const container = createEl('div', { cls: 'focus-manager' });
const container = createEl('div', { cls: 'kga-focus-manager' });
this.containerElement = container;
// 포커스 하이라이트용 스타일 추가
@ -409,7 +409,7 @@ export class FocusManager implements IPopupComponent {
100% { opacity: 1; }
}
.${this.FOCUS_CLASS}.focus-pulse {
.${this.FOCUS_CLASS}.kga-focus-pulse {
animation: focusPulse 1s ease-in-out infinite;
}
`;

View file

@ -64,7 +64,7 @@ export class KeyboardManager implements IPopupComponent {
}
render(): HTMLElement {
const container = createEl('div', { cls: 'keyboard-manager' });
const container = createEl('div', { cls: 'kga-keyboard-manager' });
this.containerElement = container;
// 키보드 힌트 표시 (데스크톱에서만)
@ -337,7 +337,7 @@ export class KeyboardManager implements IPopupComponent {
}
private createKeyboardHints(): HTMLElement {
const hintsContainer = createEl('div', { cls: 'keyboard-hints' });
const hintsContainer = createEl('div', { cls: 'kga-keyboard-hints' });
const hints = [
{ key: 'Tab', desc: '다음 오류' },
@ -349,12 +349,12 @@ export class KeyboardManager implements IPopupComponent {
];
hints.forEach(hint => {
const hintElement = createEl('div', { cls: 'keyboard-hint' });
const hintElement = createEl('div', { cls: 'kga-keyboard-hint' });
const keyElement = createEl('kbd', { cls: 'keyboard-hint-key' });
const keyElement = createEl('kbd', { cls: 'kga-keyboard-hint-key' });
keyElement.textContent = hint.key;
const descElement = createEl('span', { cls: 'keyboard-hint-desc' });
const descElement = createEl('span', { cls: 'kga-keyboard-hint-desc' });
descElement.textContent = hint.desc;
hintElement.appendChild(keyElement);
@ -370,7 +370,7 @@ export class KeyboardManager implements IPopupComponent {
const container = this.containerElement;
if (!container) return;
const hintsContainer = container.querySelector('.keyboard-hints');
const hintsContainer = container.querySelector('.kga-keyboard-hints');
if (hintsContainer && !this.shouldShowKeyboardHints()) {
hintsContainer.remove();
} else if (!hintsContainer && this.shouldShowKeyboardHints()) {

View file

@ -354,9 +354,9 @@ export class HeaderRenderer implements IPopupComponent {
// 활성화/비활성화 상태 클래스 추가
if (options.disabled) {
button.classList.add('header-button-disabled');
button.classList.add('kga-header-button-disabled');
} else {
button.classList.add('header-button-enabled');
button.classList.add('kga-header-button-enabled');
}
// 아이콘 추가
@ -443,12 +443,12 @@ export class HeaderRenderer implements IPopupComponent {
// 버튼 상태 업데이트
if (this.isAiAnalyzing) {
this.aiButtonElement.setAttribute('disabled', 'true');
this.aiButtonElement.classList.add('header-button-disabled');
this.aiButtonElement.classList.remove('header-button-enabled');
this.aiButtonElement.classList.add('kga-header-button-disabled');
this.aiButtonElement.classList.remove('kga-header-button-enabled');
} else {
this.aiButtonElement.removeAttribute('disabled');
this.aiButtonElement.classList.remove('header-button-disabled');
this.aiButtonElement.classList.add('header-button-enabled');
this.aiButtonElement.classList.remove('kga-header-button-disabled');
this.aiButtonElement.classList.add('kga-header-button-enabled');
}
// 아이콘 애니메이션 (분석 중일 때)
@ -593,12 +593,12 @@ export class HeaderRenderer implements IPopupComponent {
if (enabled) {
button.removeAttribute('disabled');
button.classList.remove('header-button-disabled');
button.classList.add('header-button-enabled');
button.classList.remove('kga-header-button-disabled');
button.classList.add('kga-header-button-enabled');
} else {
button.setAttribute('disabled', 'true');
button.classList.add('header-button-disabled');
button.classList.remove('header-button-enabled');
button.classList.add('kga-header-button-disabled');
button.classList.remove('kga-header-button-enabled');
}
Logger.debug('HeaderRenderer: 버튼 활성화 상태 변경', { type, enabled });

View file

@ -853,7 +853,7 @@ export class PreviewRenderer implements IPopupComponent {
span.textContent = segment.text;
if (segment.correctionIndex !== undefined) {
span.className = `clickable-error ${segment.className || ''}`;
span.className = `kga-clickable-error ${segment.className || ''}`;
span.dataset.correctionIndex = segment.correctionIndex.toString();
span.dataset.uniqueId = segment.uniqueId || '';
span.setAttribute('tabindex', '0');

View file

@ -286,7 +286,7 @@ export class ComponentManager {
correction: PageCorrection,
renderContext: RenderContext
): Promise<HTMLElement> {
const template = this.templates.get('error-card');
const template = this.templates.get('kga-error-card');
if (!template) {
return this.createBasicSummaryComponent(correction, renderContext);
}
@ -302,7 +302,7 @@ export class ComponentManager {
renderContext: RenderContext
): HTMLElement {
const card = createEl('div', {
cls: 'error-card',
cls: 'kga-error-card',
attr: {
'data-correction-index': correction.originalIndex.toString(),
'role': 'button',
@ -313,21 +313,21 @@ export class ComponentManager {
// 상태 표시기
const stateIndicator = createEl('div', {
cls: 'error-state-indicator',
cls: 'kga-error-state-indicator',
text: this.getStateDisplayText(renderContext.state)
});
card.appendChild(stateIndicator);
// 원본 텍스트
const originalText = createEl('div', {
cls: 'error-original-text',
cls: 'kga-error-original-text',
text: correction.correction.original
});
card.appendChild(originalText);
// 수정 제안
if (correction.correction.suggestions && correction.correction.suggestions.length > 0) {
const suggestions = createEl('div', { cls: 'error-suggestions' });
const suggestions = createEl('div', { cls: 'kga-error-suggestions' });
correction.correction.suggestions.forEach((suggestion, index) => {
const suggestionSpan = createEl('span', {
cls: 'suggestion-item',
@ -511,13 +511,13 @@ export class ComponentManager {
*/
private initializeTemplates(): void {
// 오류 카드 템플릿
this.templates.set('error-card', {
name: 'error-card',
this.templates.set('kga-error-card', {
name: 'kga-error-card',
html: `
<div class="error-card state-{{state}}" data-correction-index="{{correctionIndex}}" role="button" tabindex="0">
<div class="error-state-indicator">{{stateText}}</div>
<div class="error-original-text">{{originalText}}</div>
<div class="error-suggestions"></div>
<div class="kga-error-card state-{{state}}" data-correction-index="{{correctionIndex}}" role="button" tabindex="0">
<div class="kga-error-state-indicator">{{stateText}}</div>
<div class="kga-error-original-text">{{originalText}}</div>
<div class="kga-error-suggestions"></div>
</div>
`,
bindings: {}

View file

@ -170,9 +170,9 @@ export class InteractionHandler {
}
// 상태 표시 업데이트
const stateIndicator = summaryElement.querySelector('.error-state-indicator');
const stateIndicator = summaryElement.querySelector('.kga-error-state-indicator');
if (stateIndicator) {
stateIndicator.className = `error-state-indicator state-${newState}`;
stateIndicator.className = `kga-error-state-indicator state-${newState}`;
stateIndicator.textContent = this.getStateDisplayText(newState);
}

View file

@ -1,6 +1,6 @@
import { EditorView, WidgetType, Decoration, DecorationSet } from '@codemirror/view';
import { StateField, StateEffect } from '@codemirror/state';
import { Correction, InlineError, ExtendedWindow, PluginInstance, PluginSettings } from '../types/interfaces';
import { Correction, InlineError, ExtendedWindow, PluginSettings } from '../types/interfaces';
import { Logger } from '../utils/logger';
import { globalInlineTooltip } from '../ui/inlineTooltip';
import { Scope, App, Platform } from 'obsidian';
@ -500,13 +500,14 @@ export class InlineModeService {
// 🔧 레거시: 기존 키보드 스코프 방식 (Command Palette 방식으로 대체됨)
// private static keyboardScope: Scope | null = null;
private static app: App | null = null;
private static saveSettingsCallback: ((settings: PluginSettings) => Promise<void>) | null = null;
private static currentHoveredError: InlineError | null = null;
private static hoverTimeout: NodeJS.Timeout | null = null;
/**
*
*/
static setEditorView(view: EditorView, settings?: PluginSettings, app?: App): void {
static setEditorView(view: EditorView, settings?: PluginSettings, app?: App, saveSettingsCallback?: (settings: PluginSettings) => Promise<void>): void {
// 🔧 새로운 에디터뷰가 이전과 다르면 이전 상태 완전 정리
if (this.currentView && this.currentView !== view) {
Logger.debug('인라인 모드: 이전 에디터뷰와 다름 - 상태 정리 중');
@ -521,7 +522,10 @@ export class InlineModeService {
if (app) {
this.app = app;
}
if (saveSettingsCallback) {
this.saveSettingsCallback = saveSettingsCallback;
}
// 이벤트 리스너 추가
this.setupEventListeners(view);
@ -2188,7 +2192,6 @@ export class InlineModeService {
* (Command Palette )
*/
static registerCommands(plugin: any): void {
Logger.log('🎹 인라인 모드: 명령어 등록 시작');
// 다음 오류로 이동
plugin.addCommand({
@ -2402,17 +2405,6 @@ export class InlineModeService {
}
}
});
Logger.log('🎹 인라인 모드: 명령어 등록 완료!');
Logger.log('📋 등록된 명령어:');
Logger.log(' • Korean Grammar Assistant: 다음 문법 오류로 이동');
Logger.log(' • Korean Grammar Assistant: 이전 문법 오류로 이동');
Logger.log(' • Korean Grammar Assistant: 다음 제안 선택');
Logger.log(' • Korean Grammar Assistant: 이전 제안 선택');
Logger.log(' • Korean Grammar Assistant: 선택된 제안 적용');
Logger.log(' • Korean Grammar Assistant: 문법 오류 포커스 해제');
Logger.log(' • Korean Grammar Assistant: 한국어 문법 인라인 모드 토글');
Logger.log('💡 Command Palette (Cmd+P)에서 검색하거나 Hotkeys에서 단축키를 설정하세요!');
}
/**
@ -2945,9 +2937,7 @@ export class InlineModeService {
});
// AI 분석 서비스가 있는지 확인
const aiService = getExtendedWindow().koreanGrammarPlugin?.instance;
if (!aiService) {
if (!this.settings) {
throw new Error('AI 분석 서비스를 찾을 수 없습니다.');
}
@ -3219,11 +3209,10 @@ export class InlineModeService {
updatedSettings = IgnoredWordsService.addIgnoredWord(word, updatedSettings);
}
// 설정 업데이트 (플러그인 인스턴스를 통해)
if ((window as any).koreanGrammarPlugin?.instance) {
const plugin = (window as any).koreanGrammarPlugin.instance;
plugin.settings = updatedSettings;
await plugin.saveSettings();
// 설정 업데이트 (saveSettingsCallback을 통해)
if (this.saveSettingsCallback) {
this.settings = updatedSettings;
await this.saveSettingsCallback(updatedSettings);
Logger.log(`🔵 예외처리 사전 등록: ${wordsToIgnore.join(', ')}`);
}
}
@ -3305,18 +3294,10 @@ export class InlineModeService {
// 1. 예외처리 사전에 단어 추가
const updatedSettings = IgnoredWordsService.addIgnoredWord(trimmedWord, this.settings);
// 2. 설정 저장
const pluginWrapper = getExtendedWindow().koreanGrammarPlugin;
if (pluginWrapper && typeof pluginWrapper === 'object' && 'instance' in pluginWrapper) {
const plugin = pluginWrapper.instance;
if (plugin && typeof plugin === 'object' && 'saveSettings' in plugin) {
const saveMethod = (plugin as any).saveSettings;
if (typeof saveMethod === 'function') {
(plugin as any).settings = updatedSettings;
await saveMethod.call(plugin);
}
}
this.settings = updatedSettings; // 로컬 설정도 업데이트
// 2. 설정 저장 (saveSettingsCallback을 통해)
if (this.saveSettingsCallback) {
this.settings = updatedSettings;
await this.saveSettingsCallback(updatedSettings);
Logger.debug(`🔵 예외처리 사전에 저장됨: "${trimmedWord}"`);
}

View file

@ -308,9 +308,6 @@ export interface ExtendedWindow extends Window {
isHovered?: boolean;
};
tooltipKeepOpenMode?: boolean;
koreanGrammarPlugin?: {
instance?: unknown;
};
Notice?: new (message: string, timeout?: number) => void;
sanitizeHTMLToDom?: (html: string) => DocumentFragment;
getEventListeners?: (element: Element) => Record<string, unknown[]>;
@ -351,11 +348,3 @@ export interface EventContext {
platform?: 'desktop' | 'mobile';
}
/**
* Obsidian Plugin
*/
export interface PluginInstance {
settings?: PluginSettings;
orchestrator?: unknown;
instance?: unknown;
}

View file

@ -85,7 +85,7 @@ export class CorrectionPopup extends BaseComponent {
// Enter: 현재 선택된 수정사항 적용 (편집 중이 아닐 때만)
this.keyboardScope.register([], 'Enter', (evt: KeyboardEvent) => {
const target = evt.target as HTMLElement;
if (target && (target.dataset?.editMode === 'true' || target.classList.contains('error-original-input'))) {
if (target && (target.dataset?.editMode === 'true' || target.classList.contains('kga-error-original-input'))) {
// 편집 중인 input 요소에서는 기본 동작 허용
Logger.debug('Enter key in edit mode - allowing default behavior');
return true;
@ -98,7 +98,7 @@ export class CorrectionPopup extends BaseComponent {
// Escape: 팝업 닫기 (편집 중이 아닐 때만)
this.keyboardScope.register([], 'Escape', (evt: KeyboardEvent) => {
const target = evt.target as HTMLElement;
if (target && (target.dataset?.editMode === 'true' || target.classList.contains('error-original-input'))) {
if (target && (target.dataset?.editMode === 'true' || target.classList.contains('kga-error-original-input'))) {
// 편집 중인 input 요소에서는 기본 동작 허용
Logger.debug('Escape key in edit mode - allowing default behavior');
return true;
@ -469,9 +469,9 @@ export class CorrectionPopup extends BaseComponent {
Logger.debug(`currentFocusIndex: ${this.currentFocusIndex}`);
// 기존 포커스 하이라이트 제거
const prevFocused = this.element.querySelectorAll('.keyboard-focused');
const prevFocused = this.element.querySelectorAll('.kga-keyboard-focused');
Logger.debug(`기존 포커스 요소 ${prevFocused.length}개 제거`);
prevFocused.forEach(el => el.removeClass('keyboard-focused'));
prevFocused.forEach(el => el.removeClass('kga-keyboard-focused'));
// 현재 포커스 항목 하이라이트
if (this.currentCorrections.length > 0 &&
@ -500,7 +500,7 @@ export class CorrectionPopup extends BaseComponent {
}
if (errorItem) {
errorItem.addClass('keyboard-focused');
errorItem.addClass('kga-keyboard-focused');
// 스크롤하여 보이게 하기
errorItem.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
Logger.debug(`포커스 하이라이트 적용 성공: 고유 ID ${uniqueId}, 원본 인덱스 ${actualIndex}, 절대 위치 ${pageCorrection.absolutePosition}`);
@ -588,40 +588,40 @@ export class CorrectionPopup extends BaseComponent {
this.element.empty();
// Popup overlay
const overlay = this.element.createDiv('popup-overlay');
const overlay = this.element.createDiv('kga-popup-overlay');
// Popup content
const content = this.element.createDiv('popup-content');
const content = this.element.createDiv('kga-popup-content');
// Header
const header = content.createDiv('header');
const header = content.createDiv('kga-header');
new Setting(header).setName('한국어 맞춤법 검사').setHeading();
const headerTop = header.createDiv('preview-header-top');
const headerTop = header.createDiv('kga-preview-header-top');
// AI 분석 버튼 (항상 표시, 상태에 따라 활성화/비활성화)
const aiBtn = headerTop.createEl('button', {
cls: 'ai-analyze-btn',
cls: 'kga-ai-analyze-btn',
attr: { id: 'aiAnalyzeBtn' }
});
// AI 서비스 상태에 따른 버튼 설정
this.updateAiButtonState(aiBtn);
headerTop.createEl('button', { cls: 'close-btn-header', text: '×' });
headerTop.createEl('button', { cls: 'kga-close-btn-header', text: '×' });
// Main content
const mainContent = content.createDiv('content');
const mainContent = content.createDiv('kga-content');
// Preview section
const previewSection = mainContent.createDiv('preview-section');
const previewHeader = previewSection.createDiv('preview-header');
const previewSection = mainContent.createDiv('kga-preview-section');
const previewHeader = previewSection.createDiv('kga-preview-header');
const previewLabel = previewHeader.createDiv('preview-label');
const previewLabel = previewHeader.createDiv('kga-preview-label');
previewLabel.createSpan({ text: '미리보기' });
previewLabel.createSpan({ cls: 'preview-hint', text: '클릭하여 수정사항 적용' });
previewLabel.createSpan({ cls: 'kga-preview-hint', text: '클릭하여 수정사항 적용' });
// Color legend
const colorLegend = previewHeader.createDiv('color-legend');
const colorLegend = previewHeader.createDiv('kga-color-legend');
const legendItems = [
{ cls: 'error', text: '오류' },
{ cls: 'corrected', text: '수정' },
@ -631,8 +631,8 @@ export class CorrectionPopup extends BaseComponent {
];
legendItems.forEach(item => {
const legendItem = colorLegend.createDiv('color-legend-item');
legendItem.createDiv(`color-legend-dot ${item.cls}`);
const legendItem = colorLegend.createDiv('kga-color-legend-item');
legendItem.createDiv(`kga-color-legend-dot ${item.cls}`);
legendItem.createSpan({ text: item.text });
});
@ -641,35 +641,35 @@ export class CorrectionPopup extends BaseComponent {
this.createPaginationElement(paginationDiv);
// Preview content
const previewContent = previewSection.createDiv('preview-text');
const previewContent = previewSection.createDiv('kga-preview-text');
previewContent.id = 'resultPreview';
previewContent.createEl('span', { text: this.config.selectedText.trim() });
// Error summary
const errorSummary = mainContent.createDiv('error-summary collapsed');
const errorSummary = mainContent.createDiv('kga-error-summary collapsed');
errorSummary.id = 'errorSummary';
const errorToggle = errorSummary.createDiv('error-summary-toggle');
const leftSection = errorToggle.createDiv('left-section');
leftSection.createSpan({ cls: 'error-summary-label', text: '오류 상세' });
const errorToggle = errorSummary.createDiv('kga-error-summary-toggle');
const leftSection = errorToggle.createDiv('kga-left-section');
leftSection.createSpan({ cls: 'kga-error-summary-label', text: '오류 상세' });
const badge = leftSection.createSpan({
cls: 'error-count-badge',
cls: 'kga-error-count-badge',
text: this.getErrorStateCount().toString(),
attr: { id: 'errorCountBadge' }
});
errorToggle.createSpan({ cls: 'toggle-icon', text: '▼' });
errorToggle.createSpan({ cls: 'kga-toggle-icon', text: '▼' });
const errorContent = errorSummary.createDiv('error-summary-content');
const errorContent = errorSummary.createDiv('kga-error-summary-content');
errorContent.id = 'errorSummaryContent';
// Error summary content
this.createErrorSummaryElement(errorContent);
// Button area
const buttonArea = content.createDiv('button-area');
buttonArea.createEl('button', { cls: 'cancel-btn', text: '취소' });
buttonArea.createEl('button', {
cls: 'apply-btn',
const buttonArea = content.createDiv('kga-button-area');
buttonArea.createEl('button', { cls: 'kga-cancel-btn', text: '취소' });
buttonArea.createEl('button', {
cls: 'kga-apply-btn',
text: '적용',
attr: { id: 'applyCorrectionsButton' }
});
@ -683,17 +683,17 @@ export class CorrectionPopup extends BaseComponent {
container.empty();
if (!this.isLongText || this.totalPreviewPages <= 1) {
const hiddenContainer = container.createDiv('pagination-container-hidden');
const hiddenContainer = container.createDiv('kga-pagination-container-hidden');
hiddenContainer.id = 'paginationContainer';
return;
}
const paginationControls = container.createDiv('pagination-controls');
const paginationControls = container.createDiv('kga-pagination-controls');
paginationControls.id = 'paginationContainer';
// Previous button
const prevBtn = paginationControls.createEl('button', {
cls: 'pagination-btn',
cls: 'kga-pagination-btn',
text: '이전'
});
prevBtn.id = 'prevPreviewPage';
@ -702,13 +702,13 @@ export class CorrectionPopup extends BaseComponent {
}
// Page info
const pageInfo = paginationControls.createSpan('page-info');
const pageInfo = paginationControls.createSpan('kga-page-info');
pageInfo.id = 'previewPageInfo';
pageInfo.textContent = `${this.currentPreviewPage + 1} / ${this.totalPreviewPages}`;
// Next button
const nextBtn = paginationControls.createEl('button', {
cls: 'pagination-btn',
cls: 'kga-pagination-btn',
text: '다음'
});
nextBtn.id = 'nextPreviewPage';
@ -717,7 +717,7 @@ export class CorrectionPopup extends BaseComponent {
}
// Page chars info
const pageCharsInfo = paginationControls.createSpan('page-chars-info');
const pageCharsInfo = paginationControls.createSpan('kga-page-chars-info');
pageCharsInfo.id = 'pageCharsInfo';
pageCharsInfo.textContent = `${this.charsPerPage}`;
}
@ -728,15 +728,15 @@ export class CorrectionPopup extends BaseComponent {
*/
private createPaginationHTML(): string {
if (!this.isLongText || this.totalPreviewPages <= 1) {
return '<div id="paginationContainer" class="pagination-container-hidden"></div>';
return '<div id="paginationContainer" class="kga-pagination-container-hidden"></div>';
}
return `
<div class="pagination-controls" id="paginationContainer">
<button class="pagination-btn" id="prevPreviewPage" ${this.currentPreviewPage === 0 ? 'disabled' : ''}></button>
<span class="page-info" id="previewPageInfo">${this.currentPreviewPage + 1} / ${this.totalPreviewPages}</span>
<button class="pagination-btn" id="nextPreviewPage" ${this.currentPreviewPage === this.totalPreviewPages - 1 ? 'disabled' : ''}></button>
<span class="page-chars-info" id="pageCharsInfo">${this.charsPerPage}</span>
<div class="kga-pagination-controls" id="paginationContainer">
<button class="kga-pagination-btn" id="prevPreviewPage" ${this.currentPreviewPage === 0 ? 'disabled' : ''}></button>
<span class="kga-page-info" id="previewPageInfo">${this.currentPreviewPage + 1} / ${this.totalPreviewPages}</span>
<button class="kga-pagination-btn" id="nextPreviewPage" ${this.currentPreviewPage === this.totalPreviewPages - 1 ? 'disabled' : ''}></button>
<span class="kga-page-chars-info" id="pageCharsInfo">${this.charsPerPage}</span>
</div>
`;
}
@ -1118,7 +1118,7 @@ export class CorrectionPopup extends BaseComponent {
Logger.debug(`🎨 미리보기 사용자편집: index=${actualIndex}, original="${correction.original}", currentValue="${currentValue}", displayClass="${displayClass}"`);
}
const replacementHtml = `<span class="${displayClass} clickable-error" data-correction-index="${actualIndex}" data-unique-id="${uniqueId}">${escapedValue}</span>`;
const replacementHtml = `<span class="${displayClass} kga-clickable-error" data-correction-index="${actualIndex}" data-unique-id="${uniqueId}">${escapedValue}</span>`;
// 정확한 위치에서 오류 텍스트 찾기
const expectedText = correction.original || '';
@ -1230,10 +1230,10 @@ export class CorrectionPopup extends BaseComponent {
if (currentCorrections.length === 0) {
Logger.debug(`🏗️ 오류 없음 - 플레이스홀더 반환`);
return `
<div class="error-placeholder">
<div class="placeholder-icon"></div>
<div class="placeholder-text"> </div>
<div class="placeholder-subtext"> </div>
<div class="kga-error-placeholder">
<div class="kga-placeholder-icon"></div>
<div class="kga-placeholder-text"> </div>
<div class="kga-placeholder-subtext"> </div>
</div>
`;
}
@ -1253,18 +1253,18 @@ export class CorrectionPopup extends BaseComponent {
const aiResult = this.aiAnalysisResults.find(result => result.correctionIndex === actualIndex);
const reasoningHTML = aiResult
? `<div class="ai-analysis-result">
<div class="ai-confidence">🤖 : <span class="confidence-score">${aiResult.confidence}%</span></div>
<div class="ai-reasoning">${escapeHtml(aiResult.reasoning)}</div>
? `<div class="kga-ai-analysis-result">
<div class="kga-ai-confidence">🤖 : <span class="kga-confidence-score">${aiResult.confidence}%</span></div>
<div class="kga-ai-reasoning">${escapeHtml(aiResult.reasoning)}</div>
</div>`
: isOriginalKept
? `<div class="ai-analysis-result">
<div class="ai-reasoning"> , .</div>
? `<div class="kga-ai-analysis-result">
<div class="kga-ai-reasoning"> , .</div>
</div>`
: '';
const suggestionsHTML = suggestions.map(suggestion =>
`<span class="suggestion-compact ${this.stateManager.isSelected(actualIndex, suggestion) ? 'selected' : ''}"
`<span class="kga-suggestion-compact ${this.stateManager.isSelected(actualIndex, suggestion) ? 'selected' : ''}"
data-value="${escapeHtml(suggestion)}"
data-correction="${actualIndex}"
${isOriginalKept ? 'disabled' : ''}>
@ -1278,12 +1278,12 @@ export class CorrectionPopup extends BaseComponent {
this.stateManager.getValue(actualIndex) !== correction.original ? 'corrected' : '';
const htmlString = `
<div class="error-item-compact ${isOriginalKept ? 'spell-original-kept' : ''}" data-correction-index="${actualIndex}">
<div class="error-row">
<div class="error-original-compact ${stateClass}" data-correction-index="${actualIndex}">${escapeHtml(this.stateManager.getValue(actualIndex))}</div>
<div class="error-suggestions-compact">
<div class="kga-error-item-compact ${isOriginalKept ? 'spell-original-kept' : ''}" data-correction-index="${actualIndex}">
<div class="kga-error-row">
<div class="kga-error-original-compact ${stateClass}" data-correction-index="${actualIndex}">${escapeHtml(this.stateManager.getValue(actualIndex))}</div>
<div class="kga-error-suggestions-compact">
${suggestionsHTML}
<span class="suggestion-compact ${this.stateManager.isSelected(actualIndex, correction.original) ? 'selected' : ''} keep-original"
<span class="kga-suggestion-compact ${this.stateManager.isSelected(actualIndex, correction.original) ? 'selected' : ''} kga-keep-original"
data-value="${escapeHtml(correction.original)}"
data-correction="${actualIndex}"
${isOriginalKept ? 'disabled' : ''}>
@ -1291,7 +1291,7 @@ export class CorrectionPopup extends BaseComponent {
</span>
</div>
</div>
<div class="error-help-compact">${escapeHtml(correction.help)}</div>
<div class="kga-error-help-compact">${escapeHtml(correction.help)}</div>
${reasoningHTML}
</div>
`;
@ -1393,7 +1393,7 @@ export class CorrectionPopup extends BaseComponent {
this.bindCloseEvents();
// 팝업 오버레이 클릭 시 닫기
const overlay = this.element.querySelector('.popup-overlay');
const overlay = this.element.querySelector('.kga-popup-overlay');
if (overlay) {
this.addEventListener(overlay as HTMLElement, 'click', () => {
this.close();
@ -1420,7 +1420,7 @@ export class CorrectionPopup extends BaseComponent {
* .
*/
private bindCloseEvents(): void {
const closeButtons = this.element.querySelectorAll('.close-btn-header, .cancel-btn');
const closeButtons = this.element.querySelectorAll('.kga-close-btn-header, .kga-cancel-btn');
closeButtons.forEach(button => {
this.addEventListener(button as HTMLElement, 'click', () => {
this.close();
@ -1469,7 +1469,7 @@ export class CorrectionPopup extends BaseComponent {
* .
*/
private bindErrorToggleEvents(): void {
const toggleElement = this.element.querySelector('.error-summary-toggle');
const toggleElement = this.element.querySelector('.kga-error-summary-toggle');
if (toggleElement) {
this.addEventListener(toggleElement as HTMLElement, 'click', () => {
const errorSummary = this.element.querySelector('#errorSummary');
@ -1496,19 +1496,19 @@ export class CorrectionPopup extends BaseComponent {
Logger.debug(`🖱️ 클릭 이벤트 발생: target="${target.tagName}.${target.className}", textContent="${target.textContent}"`);
// 미리보기 영역 클릭 처리
if (target.classList.contains('clickable-error')) {
if (target.classList.contains('kga-clickable-error')) {
Logger.debug(`🖱️ 미리보기 클릭 처리: ${target.textContent}`);
this.handlePreviewClick(target);
}
// 오류 상세 카드 원본 텍스트 클릭 처리 (편집 모드)
if (target.classList.contains('error-original-compact')) {
if (target.classList.contains('kga-error-original-compact')) {
Logger.debug(`🖱️ 오류 카드 텍스트 클릭 감지: ${target.textContent}`);
this.handleCardTextClick(target);
}
// 제안 버튼 클릭 처리
if (target.classList.contains('suggestion-compact')) {
if (target.classList.contains('kga-suggestion-compact')) {
this.handleSuggestionClick(target);
}
});
@ -1518,7 +1518,7 @@ export class CorrectionPopup extends BaseComponent {
const target = e.target as HTMLElement;
// 미리보기 영역의 오류 단어에서 우클릭 시 편집 모드로 전환
if (target.classList.contains('clickable-error')) {
if (target.classList.contains('kga-clickable-error')) {
e.preventDefault(); // 기본 컨텍스트 메뉴 차단
Logger.debug(`🖱️ 미리보기 우클릭 편집 모드: ${target.textContent}`);
this.handlePreviewRightClick(target);
@ -1548,7 +1548,7 @@ export class CorrectionPopup extends BaseComponent {
const target = e.target as HTMLElement;
// 미리보기 영역의 오류 텍스트 또는 오류 카드의 원본 텍스트에서 터치홀드 처리
if (target.classList.contains('clickable-error') || target.classList.contains('error-original-compact')) {
if (target.classList.contains('kga-clickable-error') || target.classList.contains('kga-error-original-compact')) {
touchTarget = target;
touchTimer = setTimeout(() => {
@ -1562,9 +1562,9 @@ export class CorrectionPopup extends BaseComponent {
// 편집 모드 로직 먼저 호출 (미리보기가 보이는 상태에서)
let editingStarted = false;
if (touchTarget.classList.contains('clickable-error')) {
if (touchTarget.classList.contains('kga-clickable-error')) {
editingStarted = this.handlePreviewRightClick(touchTarget);
} else if (touchTarget.classList.contains('error-original-compact')) {
} else if (touchTarget.classList.contains('kga-error-original-compact')) {
editingStarted = this.handleCardTextClick(touchTarget);
}
@ -1647,19 +1647,19 @@ export class CorrectionPopup extends BaseComponent {
// 분석 중인 경우
aiBtn.textContent = '🤖 분석 중...';
aiBtn.disabled = true;
aiBtn.classList.remove('ai-disabled');
aiBtn.classList.remove('kga-ai-disabled');
aiBtn.title = 'AI 분석이 진행 중입니다...';
} else if (this.aiService && (await this.aiService.isAvailable())) {
// AI 서비스 사용 가능한 경우
aiBtn.textContent = '🤖 AI 분석';
aiBtn.disabled = false;
aiBtn.classList.remove('ai-disabled');
aiBtn.classList.remove('kga-ai-disabled');
aiBtn.title = 'AI가 최적의 수정사항을 자동으로 선택합니다 (Shift+Cmd+A)';
} else {
// AI 서비스 사용 불가능한 경우
aiBtn.textContent = '🤖 AI 미설정';
aiBtn.disabled = true;
aiBtn.classList.add('ai-disabled');
aiBtn.classList.add('kga-ai-disabled');
if (!this.aiService) {
aiBtn.title = 'AI 서비스를 초기화할 수 없습니다. 플러그인을 다시 로드해보세요.';
} else {
@ -1676,7 +1676,7 @@ export class CorrectionPopup extends BaseComponent {
Logger.error('AI 버튼 상태 업데이트 실패:', error);
aiBtn.textContent = '🤖 AI 오류';
aiBtn.disabled = true;
aiBtn.classList.add('ai-disabled');
aiBtn.classList.add('kga-ai-disabled');
aiBtn.title = 'AI 서비스 상태 확인 중 오류가 발생했습니다.';
}
}
@ -1748,7 +1748,7 @@ export class CorrectionPopup extends BaseComponent {
// DOM 업데이트 후 편집 모드 진입 (비동기 처리)
setTimeout(() => {
const errorCard = this.element.querySelector(`[data-correction-index="${correctionIndex}"] .error-original-compact`);
const errorCard = this.element.querySelector(`[data-correction-index="${correctionIndex}"] .kga-error-original-compact`);
if (errorCard) {
Logger.debug(`🔧 편집 모드 진입 - 오류 상세 카드 찾음: index=${correctionIndex}`);
@ -1802,7 +1802,7 @@ export class CorrectionPopup extends BaseComponent {
const input = document.createElement('input');
input.type = 'text';
input.value = currentText;
input.className = 'error-original-input';
input.className = 'kga-error-original-input';
input.dataset.correctionIndex = correctionIndex.toString();
input.dataset.editMode = 'true'; // 편집 모드 표시
@ -1822,7 +1822,7 @@ export class CorrectionPopup extends BaseComponent {
* .
*/
private createDesktopEditMode(originalElement: HTMLElement, input: HTMLInputElement, correctionIndex: number, getIsFinished: () => boolean, setIsFinished: (flag: boolean) => void): void {
const errorCard = originalElement.closest('.error-card');
const errorCard = originalElement.closest('.kga-error-card');
const hiddenElements: Array<{ element: HTMLElement; className: string }> = [];
const hideElement = (element: HTMLElement | null, className: string = HIDDEN_CLASS, logMessage?: string) => {
@ -1836,12 +1836,12 @@ export class CorrectionPopup extends BaseComponent {
if (errorCard) {
hideElement(
errorCard.querySelector('.error-suggestions-compact') as HTMLElement,
errorCard.querySelector('.kga-error-suggestions-compact') as HTMLElement,
HIDDEN_CLASS,
`🖥️ 수정 제안 버튼 숨김: index=${correctionIndex}`
);
hideElement(
errorCard.querySelector('.error-exception-btn') as HTMLElement,
errorCard.querySelector('.kga-error-exception-btn') as HTMLElement,
HIDDEN_CLASS,
`🖥️ 예외 처리 버튼 숨김: index=${correctionIndex}`
);
@ -1944,18 +1944,18 @@ export class CorrectionPopup extends BaseComponent {
};
// 해당 오류 카드 찾기 및 수정 제안 버튼들 숨기기
const errorCard = originalElement.closest('.error-card');
const errorCard = originalElement.closest('.kga-error-card');
if (errorCard) {
// editing-mode 클래스 추가 (CSS 폴백용)
errorCard.classList.add('editing-mode');
Logger.debug(`📱 editing-mode 클래스 추가: index=${correctionIndex}`);
// kga-editing-mode 클래스 추가 (CSS 폴백용)
errorCard.classList.add('kga-editing-mode');
Logger.debug(`📱 kga-editing-mode 클래스 추가: index=${correctionIndex}`);
// 수정 제안 버튼들 모두 찾아서 숨기기
const suggestions = errorCard.querySelectorAll('.suggestion-compact');
const keepOriginals = errorCard.querySelectorAll('.keep-original');
const suggestionsContainer = errorCard.querySelector('.error-suggestions-compact') as HTMLElement;
const exceptionBtn = errorCard.querySelector('.error-exception-btn') as HTMLElement;
const suggestions = errorCard.querySelectorAll('.kga-suggestion-compact');
const keepOriginals = errorCard.querySelectorAll('.kga-keep-original');
const suggestionsContainer = errorCard.querySelector('.kga-error-suggestions-compact') as HTMLElement;
const exceptionBtn = errorCard.querySelector('.kga-error-exception-btn') as HTMLElement;
// 개별 수정 제안 버튼들 강제 숨기기
suggestions.forEach((btn) => {
@ -1984,17 +1984,17 @@ export class CorrectionPopup extends BaseComponent {
// 컨테이너 생성 (Obsidian createEl 사용)
const container = document.createElement('div');
container.className = 'mobile-edit-container';
container.className = 'kga-mobile-edit-container';
// 완료 버튼
const saveBtn = document.createElement('button');
saveBtn.className = 'mobile-edit-btn save';
saveBtn.className = 'kga-mobile-edit-btn save';
saveBtn.textContent = '✓';
saveBtn.title = '저장';
// 취소 버튼
const cancelBtn = document.createElement('button');
cancelBtn.className = 'mobile-edit-btn cancel';
cancelBtn.className = 'kga-mobile-edit-btn cancel';
cancelBtn.textContent = '✕';
cancelBtn.title = '취소';
@ -2006,10 +2006,10 @@ export class CorrectionPopup extends BaseComponent {
// 모바일 편집 모드 종료 - 미리보기 복원 및 오류 상세 영역 원래 크기로 복원
this.exitMobileEditingMode();
// editing-mode 클래스 제거
// kga-editing-mode 클래스 제거
if (errorCard) {
errorCard.classList.remove('editing-mode');
Logger.debug(`📱 editing-mode 클래스 제거: index=${correctionIndex}`);
errorCard.classList.remove('kga-editing-mode');
Logger.debug(`📱 kga-editing-mode 클래스 제거: index=${correctionIndex}`);
}
// 숨겨둔 요소들 다시 표시
@ -2030,10 +2030,10 @@ export class CorrectionPopup extends BaseComponent {
// 모바일 편집 모드 종료 - 미리보기 복원 및 오류 상세 영역 원래 크기로 복원
this.exitMobileEditingMode();
// editing-mode 클래스 제거
// kga-editing-mode 클래스 제거
if (errorCard) {
errorCard.classList.remove('editing-mode');
Logger.debug(`📱 editing-mode 클래스 제거 (취소): index=${correctionIndex}`);
errorCard.classList.remove('kga-editing-mode');
Logger.debug(`📱 kga-editing-mode 클래스 제거 (취소): index=${correctionIndex}`);
}
// 숨겨진 요소들 다시 표시
@ -2157,7 +2157,7 @@ export class CorrectionPopup extends BaseComponent {
this.updateFocusHighlight();
// 미리보기에서 해당 단어를 화면 중앙으로 스크롤
const previewElement = this.element.querySelector('.preview-text');
const previewElement = this.element.querySelector('.kga-preview-text');
if (previewElement) {
const targetSpan = previewElement.querySelector(`[data-correction-index="${correctionIndex}"]`);
if (targetSpan) {
@ -2169,9 +2169,9 @@ export class CorrectionPopup extends BaseComponent {
Logger.debug(`🎯 미리보기 스크롤 완료: 단어 "${targetSpan.textContent}"`);
// 포커스된 요소에 일시적 하이라이트 효과
targetSpan.classList.add('edit-completion-highlight');
targetSpan.classList.add('kga-edit-completion-highlight');
setTimeout(() => {
targetSpan.classList.remove('edit-completion-highlight');
targetSpan.classList.remove('kga-edit-completion-highlight');
}, 2000);
} else {
Logger.debug(`🎯 미리보기에서 해당 단어를 찾을 수 없음: index=${correctionIndex}`);
@ -2238,11 +2238,11 @@ export class CorrectionPopup extends BaseComponent {
* .
*/
private updateErrorDetailStyles(): void {
const errorItems = this.element.querySelectorAll('.error-item-compact');
const errorItems = this.element.querySelectorAll('.kga-error-item-compact');
errorItems.forEach((item, index) => {
const correctionIndex = parseInt(item.getAttribute('data-correction-index') || '0');
const originalText = item.querySelector('.error-original-compact');
const originalText = item.querySelector('.kga-error-original-compact');
if (originalText) {
// 기존 상태 클래스 제거
@ -2284,7 +2284,7 @@ export class CorrectionPopup extends BaseComponent {
// 페이지네이션 컨테이너 가시성 업데이트
if (paginationContainer) {
if (this.isLongText && this.totalPreviewPages > 1) {
paginationContainer.className = 'pagination-controls';
paginationContainer.className = 'kga-pagination-controls';
// 페이지네이션이 표시되어야 하는데 버튼이 없으면 HTML을 다시 생성
if (!prevButton || !nextButton) {
// DOM API를 사용하여 페이지네이션 컨트롤 생성
@ -2296,7 +2296,7 @@ export class CorrectionPopup extends BaseComponent {
this.bindPaginationEvents();
}
} else {
paginationContainer.className = 'pagination-container-hidden';
paginationContainer.className = 'kga-pagination-container-hidden';
}
}
@ -2555,20 +2555,20 @@ export class CorrectionPopup extends BaseComponent {
if (currentCorrections.length === 0) {
// 오류가 없는 경우의 플레이스홀더
const placeholder = document.createElement('div');
placeholder.className = 'error-placeholder';
placeholder.className = 'kga-error-placeholder';
const icon = document.createElement('div');
icon.className = 'placeholder-icon';
icon.className = 'kga-placeholder-icon';
icon.textContent = '✓';
placeholder.appendChild(icon);
const text = document.createElement('div');
text.className = 'placeholder-text';
text.className = 'kga-placeholder-text';
text.textContent = '이 페이지에는 발견된 오류가 없습니다';
placeholder.appendChild(text);
const subtext = document.createElement('div');
subtext.className = 'placeholder-subtext';
subtext.className = 'kga-placeholder-subtext';
subtext.textContent = '다른 페이지에서 오류를 확인하세요';
placeholder.appendChild(subtext);
@ -2590,7 +2590,7 @@ export class CorrectionPopup extends BaseComponent {
// 에러 아이템 컨테이너
const errorItem = document.createElement('div');
errorItem.className = `error-item-compact ${isOriginalKept ? 'spell-original-kept' : ''}`;
errorItem.className = `kga-error-item-compact ${isOriginalKept ? 'spell-original-kept' : ''}`;
errorItem.setAttribute('data-correction-index', actualIndex.toString());
errorItem.setAttribute('data-unique-id', pageCorrection.uniqueId);
@ -2598,7 +2598,7 @@ export class CorrectionPopup extends BaseComponent {
// 에러 행 (원본 + 제안들)
const errorRow = document.createElement('div');
errorRow.className = 'error-row';
errorRow.className = 'kga-error-row';
// 원본 텍스트 (사용자 편집값 또는 현재 상태값 표시)
const errorOriginal = document.createElement('div');
@ -2608,19 +2608,19 @@ export class CorrectionPopup extends BaseComponent {
this.stateManager.isExceptionState(actualIndex) ? 'exception-processed' :
this.stateManager.getValue(actualIndex) !== correction.original ? 'corrected' : '';
errorOriginal.className = `error-original-compact ${stateClass}`;
errorOriginal.className = `kga-error-original-compact ${stateClass}`;
errorOriginal.setAttribute('data-correction-index', actualIndex.toString());
errorOriginal.textContent = this.stateManager.getValue(actualIndex); // 현재 상태값 (편집값 포함)
errorRow.appendChild(errorOriginal);
// 제안들 컨테이너
const suggestionsContainer = document.createElement('div');
suggestionsContainer.className = 'error-suggestions-compact';
suggestionsContainer.className = 'kga-error-suggestions-compact';
// 제안 스팬들 생성
suggestions.forEach(suggestion => {
const suggestionSpan = document.createElement('span');
suggestionSpan.className = `suggestion-compact ${this.stateManager.isSelected(actualIndex, suggestion) ? 'selected' : ''}`;
suggestionSpan.className = `kga-suggestion-compact ${this.stateManager.isSelected(actualIndex, suggestion) ? 'selected' : ''}`;
suggestionSpan.setAttribute('data-value', suggestion);
suggestionSpan.setAttribute('data-correction', actualIndex.toString());
if (isOriginalKept) {
@ -2632,7 +2632,7 @@ export class CorrectionPopup extends BaseComponent {
// 예외처리 스팬
const keepOriginalSpan = document.createElement('span');
keepOriginalSpan.className = `suggestion-compact ${this.stateManager.isSelected(actualIndex, correction.original) ? 'selected' : ''} keep-original`;
keepOriginalSpan.className = `kga-suggestion-compact ${this.stateManager.isSelected(actualIndex, correction.original) ? 'selected' : ''} kga-keep-original`;
keepOriginalSpan.setAttribute('data-value', correction.original);
keepOriginalSpan.setAttribute('data-correction', actualIndex.toString());
if (isOriginalKept) {
@ -2646,7 +2646,7 @@ export class CorrectionPopup extends BaseComponent {
// 도움말 텍스트
const errorHelp = document.createElement('div');
errorHelp.className = 'error-help-compact';
errorHelp.className = 'kga-error-help-compact';
errorHelp.textContent = correction.help;
errorItem.appendChild(errorHelp);
@ -2654,26 +2654,26 @@ export class CorrectionPopup extends BaseComponent {
const aiResult = this.aiAnalysisResults.find(result => result.correctionIndex === actualIndex);
if (aiResult || isOriginalKept) {
const aiAnalysis = document.createElement('div');
aiAnalysis.className = 'ai-analysis-result';
aiAnalysis.className = 'kga-ai-analysis-result';
if (aiResult) {
const aiConfidence = document.createElement('div');
aiConfidence.className = 'ai-confidence';
aiConfidence.className = 'kga-ai-confidence';
aiConfidence.textContent = '🤖 신뢰도: ';
const confidenceScore = document.createElement('span');
confidenceScore.className = 'confidence-score';
confidenceScore.className = 'kga-confidence-score';
confidenceScore.textContent = `${aiResult.confidence}%`;
aiConfidence.appendChild(confidenceScore);
aiAnalysis.appendChild(aiConfidence);
const aiReasoning = document.createElement('div');
aiReasoning.className = 'ai-reasoning';
aiReasoning.className = 'kga-ai-reasoning';
aiReasoning.textContent = aiResult.reasoning;
aiAnalysis.appendChild(aiReasoning);
} else if (isOriginalKept) {
const aiReasoning = document.createElement('div');
aiReasoning.className = 'ai-reasoning';
aiReasoning.className = 'kga-ai-reasoning';
aiReasoning.textContent = '사용자가 직접 선택했거나, 예외 단어로 등록된 항목입니다.';
aiAnalysis.appendChild(aiReasoning);
}
@ -2698,7 +2698,7 @@ export class CorrectionPopup extends BaseComponent {
const fragment = document.createDocumentFragment();
const prevButton = document.createElement('button');
prevButton.className = 'pagination-btn';
prevButton.className = 'kga-pagination-btn';
prevButton.id = 'prevPreviewPage';
prevButton.textContent = '이전';
if (this.currentPreviewPage === 0) {
@ -2707,13 +2707,13 @@ export class CorrectionPopup extends BaseComponent {
fragment.appendChild(prevButton);
const pageInfo = document.createElement('span');
pageInfo.className = 'page-info';
pageInfo.className = 'kga-page-info';
pageInfo.id = 'previewPageInfo';
pageInfo.textContent = `${this.currentPreviewPage + 1} / ${this.totalPreviewPages}`;
fragment.appendChild(pageInfo);
const nextButton = document.createElement('button');
nextButton.className = 'pagination-btn';
nextButton.className = 'kga-pagination-btn';
nextButton.id = 'nextPreviewPage';
nextButton.textContent = '다음';
if (this.currentPreviewPage === this.totalPreviewPages - 1) {
@ -2722,7 +2722,7 @@ export class CorrectionPopup extends BaseComponent {
fragment.appendChild(nextButton);
const charsInfo = document.createElement('span');
charsInfo.className = 'page-chars-info';
charsInfo.className = 'kga-page-chars-info';
charsInfo.id = 'pageCharsInfo';
charsInfo.textContent = `${this.charsPerPage}`;
fragment.appendChild(charsInfo);
@ -2736,75 +2736,75 @@ export class CorrectionPopup extends BaseComponent {
*/
private createTokenWarningModal(tokenUsage: any, isOverMaxTokens: boolean, maxTokens: number): HTMLElement {
const content = document.createElement('div');
content.className = 'token-warning-content';
content.className = 'kga-token-warning-content';
// 헤더 섹션
const header = content.appendChild(document.createElement('div'));
header.className = 'token-warning-header';
header.className = 'kga-token-warning-header';
const headerIcon = header.appendChild(document.createElement('div'));
headerIcon.className = 'token-warning-header-icon';
headerIcon.className = 'kga-token-warning-header-icon';
headerIcon.textContent = '⚡';
const headerInfo = header.appendChild(document.createElement('div'));
const title = headerInfo.appendChild(document.createElement('h3'));
title.className = 'token-warning-title';
title.className = 'kga-token-warning-title';
title.textContent = isOverMaxTokens ? '토큰 사용량 확인' : '토큰 사용량 안내';
const description = headerInfo.appendChild(document.createElement('p'));
description.className = 'token-warning-description';
description.className = 'kga-token-warning-description';
description.textContent = isOverMaxTokens ? '설정된 한계를 초과했습니다' : '예상 사용량이 높습니다';
// 토큰 사용량 카드
const details = content.appendChild(document.createElement('div'));
details.className = 'token-warning-details';
details.className = 'kga-token-warning-details';
const stats = details.appendChild(document.createElement('div'));
stats.className = 'token-warning-stats';
stats.className = 'kga-token-warning-stats';
// 총 토큰 통계
const totalTokenItem = stats.appendChild(document.createElement('div'));
totalTokenItem.className = 'token-stat-item';
totalTokenItem.className = 'kga-token-stat-item';
const totalTokenNumber = totalTokenItem.appendChild(document.createElement('div'));
totalTokenNumber.className = 'token-stat-number';
totalTokenNumber.className = 'kga-token-stat-number';
totalTokenNumber.textContent = tokenUsage.totalEstimated.toLocaleString();
const totalTokenLabel = totalTokenItem.appendChild(document.createElement('div'));
totalTokenLabel.className = 'token-stat-label';
totalTokenLabel.className = 'kga-token-stat-label';
totalTokenLabel.textContent = '총 토큰';
// 예상 비용 통계
const costItem = stats.appendChild(document.createElement('div'));
costItem.className = 'token-stat-item';
costItem.className = 'kga-token-stat-item';
const costNumber = costItem.appendChild(document.createElement('div'));
costNumber.className = 'token-stat-number orange';
costNumber.className = 'kga-token-stat-number kga-orange';
costNumber.textContent = tokenUsage.estimatedCost;
const costLabel = costItem.appendChild(document.createElement('div'));
costLabel.className = 'token-stat-label';
costLabel.className = 'kga-token-stat-label';
costLabel.textContent = '예상 비용';
// 형태소 최적화는 백그라운드에서 동작하므로 UI에 표시하지 않음
// 사용량 세부사항
const recommendation = details.appendChild(document.createElement('div'));
recommendation.className = 'token-warning-recommendation';
recommendation.className = 'kga-token-warning-recommendation';
const recHeader = recommendation.appendChild(document.createElement('div'));
recHeader.className = 'token-warning-recommendation-header';
recHeader.className = 'kga-token-warning-recommendation-header';
const recContent = recHeader.appendChild(document.createElement('div'));
recContent.className = 'token-warning-recommendation-content';
recContent.className = 'kga-token-warning-recommendation-content';
const recTitle = recContent.appendChild(document.createElement('div'));
recTitle.className = 'token-warning-recommendation-title';
recTitle.className = 'kga-token-warning-recommendation-title';
recTitle.textContent = '사용량 세부사항';
const recText = recContent.appendChild(document.createElement('div'));
recText.className = 'token-warning-recommendation-text';
recText.className = 'kga-token-warning-recommendation-text';
// 깔끔한 토큰 정보만 표시 (최적화는 백그라운드 처리)
const detailText = `입력: ${tokenUsage.inputTokens.toLocaleString()} • 출력: ${tokenUsage.estimatedOutputTokens.toLocaleString()}`;
@ -2813,51 +2813,51 @@ export class CorrectionPopup extends BaseComponent {
// 토큰 초과 알림 (조건부)
if (isOverMaxTokens) {
const overLimit = content.appendChild(document.createElement('div'));
overLimit.className = 'token-warning-over-limit';
overLimit.className = 'kga-token-warning-over-limit';
const overLimitContent = overLimit.appendChild(document.createElement('div'));
overLimitContent.className = 'token-warning-over-limit-content';
overLimitContent.className = 'kga-token-warning-over-limit-content';
const overLimitIcon = overLimitContent.appendChild(document.createElement('div'));
overLimitIcon.className = 'token-warning-over-limit-icon';
overLimitIcon.className = 'kga-token-warning-over-limit-icon';
overLimitIcon.textContent = '!';
const overLimitText = overLimitContent.appendChild(document.createElement('div'));
overLimitText.className = 'token-warning-over-limit-text';
overLimitText.className = 'kga-token-warning-over-limit-text';
const overLimitTitle = overLimitText.appendChild(document.createElement('div'));
overLimitTitle.className = 'token-warning-over-limit-title';
overLimitTitle.className = 'kga-token-warning-over-limit-title';
overLimitTitle.textContent = '설정된 최대 토큰을 초과했습니다';
const overLimitDesc = overLimitText.appendChild(document.createElement('div'));
overLimitDesc.className = 'token-warning-over-limit-description';
overLimitDesc.className = 'kga-token-warning-over-limit-description';
overLimitDesc.textContent = `현재 설정: ${maxTokens.toLocaleString()} 토큰 → 초과량: ${(tokenUsage.totalEstimated - maxTokens).toLocaleString()} 토큰`;
}
// 액션 버튼들
const actions = content.appendChild(document.createElement('div'));
actions.className = 'token-warning-actions';
actions.className = 'kga-token-warning-actions';
const cancelBtn = actions.appendChild(document.createElement('button'));
cancelBtn.id = 'token-warning-cancel';
cancelBtn.className = 'token-warning-btn token-warning-btn-cancel';
cancelBtn.className = 'kga-token-warning-btn kga-token-warning-btn-cancel';
cancelBtn.textContent = '취소';
if (isOverMaxTokens) {
const updateSettingsBtn = actions.appendChild(document.createElement('button'));
updateSettingsBtn.id = 'token-warning-update-settings';
updateSettingsBtn.className = 'token-warning-btn token-warning-btn-settings';
updateSettingsBtn.className = 'kga-token-warning-btn kga-token-warning-btn-settings';
updateSettingsBtn.textContent = '설정 업데이트';
}
const proceedBtn = actions.appendChild(document.createElement('button'));
proceedBtn.id = 'token-warning-proceed';
proceedBtn.className = 'token-warning-btn token-warning-btn-proceed';
proceedBtn.className = 'kga-token-warning-btn kga-token-warning-btn-proceed';
proceedBtn.textContent = isOverMaxTokens ? '이번만 진행' : '계속 진행';
// 키보드 단축키 안내
const keyboardHint = content.appendChild(document.createElement('div'));
keyboardHint.className = 'token-warning-keyboard-hint';
keyboardHint.className = 'kga-token-warning-keyboard-hint';
keyboardHint.textContent = '💡 키보드 단축키: enter(진행), esc(취소)';
return content;
@ -2985,7 +2985,7 @@ export class CorrectionPopup extends BaseComponent {
// 확인 모달 표시
return new Promise((resolve) => {
const modal = document.createElement('div');
modal.className = 'token-warning-modal';
modal.className = 'kga-token-warning-modal';
// DOM API를 사용하여 모달 내용 생성
const modalContent = this.createTokenWarningModal(tokenUsage, isOverMaxTokens, maxTokens);
@ -3152,20 +3152,20 @@ export class CorrectionPopup extends BaseComponent {
}
const hint = document.createElement('div');
hint.className = 'keyboard-navigation-hint';
hint.className = 'kga-keyboard-navigation-hint';
hint.id = 'keyboard-hint';
// 헤더 (제목 + 닫기 버튼)
const header = document.createElement('div');
header.className = 'hint-header';
header.className = 'kga-hint-header';
const title = document.createElement('div');
title.className = 'hint-title';
title.className = 'kga-hint-title';
title.textContent = '⌨️ 키보드 단축키';
header.appendChild(title);
const closeBtn = document.createElement('button');
closeBtn.className = 'hint-close-btn';
closeBtn.className = 'kga-hint-close-btn';
closeBtn.textContent = '×';
closeBtn.title = '단축키 가이드 닫기';
closeBtn.addEventListener('click', () => {
@ -3191,14 +3191,14 @@ export class CorrectionPopup extends BaseComponent {
shortcuts.forEach(shortcut => {
const item = document.createElement('div');
item.className = 'hint-item';
item.className = 'kga-hint-item';
const key = document.createElement('span');
key.className = 'hint-key';
key.className = 'kga-hint-key';
key.textContent = shortcut.key;
const desc = document.createElement('span');
desc.className = 'hint-desc';
desc.className = 'kga-hint-desc';
desc.textContent = shortcut.desc;
item.appendChild(key);
@ -3255,7 +3255,7 @@ export class CorrectionPopup extends BaseComponent {
const errorSummary = document.getElementById('errorSummary');
if (!errorSummary) return;
const errorItems = errorSummary.querySelectorAll('.error-item-compact');
const errorItems = errorSummary.querySelectorAll('.kga-error-item-compact');
let targetItem: HTMLElement | null = null;
// 실제 인덱스와 매칭되는 항목 찾기
@ -3303,17 +3303,17 @@ export class CorrectionPopup extends BaseComponent {
*/
private highlightFocusedError(targetItem: HTMLElement): void {
// 기존 하이라이트 제거
const existingHighlight = document.querySelector('.error-item-highlighted');
const existingHighlight = document.querySelector('.kga-error-item-highlighted');
if (existingHighlight) {
existingHighlight.classList.remove('error-item-highlighted');
existingHighlight.classList.remove('kga-error-item-highlighted');
}
// 새로운 하이라이트 추가
targetItem.classList.add('error-item-highlighted');
targetItem.classList.add('kga-error-item-highlighted');
// 2초 후 하이라이트 제거
setTimeout(() => {
targetItem.classList.remove('error-item-highlighted');
targetItem.classList.remove('kga-error-item-highlighted');
}, 2000);
Logger.log('오류 카드 하이라이트 애니메이션 적용');
@ -3363,7 +3363,7 @@ export class CorrectionPopup extends BaseComponent {
// DOM 업데이트 후 편집 모드 진입
setTimeout(() => {
const errorCard = this.element.querySelector(`[data-correction-index="${actualIndex}"] .error-original-compact`);
const errorCard = this.element.querySelector(`[data-correction-index="${actualIndex}"] .kga-error-original-compact`);
if (errorCard) {
Logger.debug(`⌨️ 편집 모드 진입 - 오류 상세 카드 찾음: index=${actualIndex}`);
@ -3381,10 +3381,10 @@ export class CorrectionPopup extends BaseComponent {
} else {
Logger.debug(`⌨️ 오류 상세 카드를 찾을 수 없음: index=${actualIndex}`);
Logger.debug(`⌨️ 재시도: 모든 .error-original-compact 요소 확인`);
Logger.debug(`⌨️ 재시도: 모든 .kga-error-original-compact 요소 확인`);
// 디버깅을 위해 모든 카드 확인
const allCards = this.element.querySelectorAll('.error-original-compact');
const allCards = this.element.querySelectorAll('.kga-error-original-compact');
Logger.debug(`⌨️ 발견된 카드 개수: ${allCards.length}`);
allCards.forEach((card, index) => {
const cardIndex = card.parentElement?.dataset?.correctionIndex;
@ -3422,7 +3422,7 @@ export class CorrectionPopup extends BaseComponent {
const currentCorrections = this.removeDuplicateCorrections(rawCorrections);
if (currentCorrections.length === 0) {
const placeholder = container.createDiv('error-placeholder');
const placeholder = container.createDiv('kga-error-placeholder');
placeholder.textContent = '현재 페이지에 오류가 없습니다.';
return;
}
@ -3431,7 +3431,7 @@ export class CorrectionPopup extends BaseComponent {
const actualIndex = pageCorrection.originalIndex;
if (actualIndex === undefined) return;
const errorCard = container.createDiv('error-card-compact');
const errorCard = container.createDiv('kga-error-card-compact');
errorCard.dataset.correctionIndex = actualIndex.toString();
// Error header
@ -3439,7 +3439,7 @@ export class CorrectionPopup extends BaseComponent {
const errorNumber = errorHeader.createSpan('error-number');
errorNumber.textContent = (index + 1).toString();
const errorOriginal = errorHeader.createSpan('error-original-compact');
const errorOriginal = errorHeader.createSpan('kga-error-original-compact');
errorOriginal.textContent = pageCorrection.correction.original;
const errorArrow = errorHeader.createSpan('error-arrow');
@ -3454,7 +3454,7 @@ export class CorrectionPopup extends BaseComponent {
// Help text
if (pageCorrection.correction.help) {
const helpDiv = errorCard.createDiv('error-help-compact');
const helpDiv = errorCard.createDiv('kga-error-help-compact');
helpDiv.textContent = pageCorrection.correction.help;
}
@ -3501,7 +3501,7 @@ export class CorrectionPopup extends BaseComponent {
const uniqueId = pageCorrection?.uniqueId || 'unknown';
span.textContent = currentValue;
span.className = `clickable-error ${displayClass}`;
span.className = `kga-clickable-error ${displayClass}`;
span.dataset.correctionIndex = actualIndex.toString();
span.dataset.uniqueId = uniqueId;
span.setAttribute('tabindex', '0');

View file

@ -12,10 +12,6 @@ interface InlineTooltipWindow extends Window {
tooltipProtected?: boolean;
tooltipKeepOpenMode?: boolean;
app?: App;
InlineModeService?: {
addWordToIgnoreListAndRemoveErrors(word: string): Promise<number>;
removeError(view: any, uniqueId: string): void;
};
globalInlineTooltip?: InlineTooltip;
}
@ -337,7 +333,7 @@ export class InlineTooltip {
Logger.log('📱 모바일 고정 위치 툴팁 활성화');
// AI 분석 영역이 있는지 확인
const hasAIAnalysis = this.tooltip.querySelector('.tooltip-ai-area') !== null;
const hasAIAnalysis = this.tooltip.querySelector('.kga-tooltip-ai-area') !== null;
// 컴팩트한 크기 설정 (AI 분석에 따라 높이 조정)
const fixedWidth = viewportWidth - 16; // 양쪽 8px씩만 마진
@ -687,20 +683,20 @@ export class InlineTooltip {
}
// 헤더 영역 - 닫기 버튼 포함
const header = this.tooltip.createEl('div', { cls: 'tooltip-header' });
const header = this.tooltip.createEl('div', { cls: 'kga-tooltip-header' });
if (isMobile) header.classList.add('kga-mobile');
if (isPhone) header.classList.add('kga-mobile-phone');
// 🔧 헤더 텍스트 (필터링된 개수 반영)
const headerText = header.createEl('span', {
text: `${uniqueOriginalErrors.length}개 오류 병합됨`,
cls: 'header-text'
cls: 'kga-header-text'
});
// 우상단 닫기 버튼 (✕) - 순수 아이콘만
const headerCloseButton = header.createEl('button', {
text: '✕',
cls: 'header-close-button'
cls: 'kga-header-close-button'
});
if (isMobile) headerCloseButton.classList.add('kga-mobile');
if (isPhone) headerCloseButton.classList.add('kga-mobile-phone');
@ -728,26 +724,26 @@ export class InlineTooltip {
});
// 스크롤 가능한 내용 영역 - 모바일 최적화
const scrollContainer = this.tooltip.createEl('div', { cls: 'tooltip-scroll-container' });
const scrollContainer = this.tooltip.createEl('div', { cls: 'kga-tooltip-scroll-container' });
if (isMobile) scrollContainer.classList.add('kga-mobile');
if (isPhone) scrollContainer.classList.add('kga-mobile-phone');
// 🔍 각 원본 오류별로 섹션 생성 - 모바일 최적화 (이미 중복 제거됨)
uniqueOriginalErrors.forEach((originalError, index) => {
const errorSection = scrollContainer.createEl('div', { cls: 'error-section' });
const errorSection = scrollContainer.createEl('div', { cls: 'kga-error-section' });
if (isMobile) errorSection.classList.add('kga-mobile');
if (isPhone) errorSection.classList.add('kga-mobile-phone');
if (index > 0) errorSection.classList.add('kga-bordered');
// 한 줄 레이아웃 (오류 → 제안들) - 모바일 최적화
const errorLine = errorSection.createEl('div', { cls: 'error-line' });
const errorLine = errorSection.createEl('div', { cls: 'kga-error-line' });
if (isMobile) errorLine.classList.add('kga-mobile');
if (isPhone) errorLine.classList.add('kga-mobile-phone');
// 오류 단어 표시 (고정 너비) - 모바일 최적화
const errorWord = errorLine.createEl('span', {
text: originalError.correction.original,
cls: 'error-word'
cls: 'kga-error-word'
});
if (isMobile) errorWord.classList.add('kga-mobile');
if (isPhone) errorWord.classList.add('kga-mobile-phone');
@ -758,7 +754,7 @@ export class InlineTooltip {
if (isPhone) arrow.classList.add('kga-mobile-phone');
// 수정 제안들을 가로로 나열 (남은 공간 활용) - 모바일 최적화
const suggestionsList = errorLine.createEl('div', { cls: 'suggestions-list' });
const suggestionsList = errorLine.createEl('div', { cls: 'kga-suggestions-list' });
if (isMobile) suggestionsList.classList.add('kga-mobile');
if (isPhone) suggestionsList.classList.add('kga-mobile-phone');
@ -769,7 +765,7 @@ export class InlineTooltip {
uniqueSuggestions.forEach((suggestion, index) => {
const suggestionButton = suggestionsList.createEl('span', {
text: suggestion,
cls: 'suggestion-button'
cls: 'kga-suggestion-button'
});
if (isMobile) suggestionButton.classList.add('kga-mobile');
if (isPhone) suggestionButton.classList.add('kga-mobile-phone');
@ -801,14 +797,14 @@ export class InlineTooltip {
// 도움말 아이콘 추가 (원본 오류에 도움말이 있는 경우)
if (originalError.correction.help) {
const helpContainer = errorLine.createEl('div', { cls: 'help-container' });
const helpContainer = errorLine.createEl('div', { cls: 'kga-help-container' });
// 📖 도움말을 하단에 표시하는 인라인 방식 사용
this.createInlineHelpIcon(originalError.correction.help, helpContainer, () => {
let helpArea = this.tooltip!.querySelector('.tooltip-help-area') as HTMLElement;
let helpArea = this.tooltip!.querySelector('.kga-tooltip-help-area') as HTMLElement;
if (!helpArea) {
// 도움말 영역 생성
helpArea = this.tooltip!.createEl('div', { cls: 'tooltip-help-area' });
helpArea = this.tooltip!.createEl('div', { cls: 'kga-tooltip-help-area' });
if (isMobile) helpArea.classList.add('kga-mobile');
if (isPhone) helpArea.classList.add('kga-mobile-phone');
helpArea.textContent = originalError.correction.help;
@ -826,25 +822,25 @@ export class InlineTooltip {
});
// 하단 액션 컨테이너 (도움말 및 버튼들) - 아이폰 최적화
const actionsContainer = this.tooltip.createEl('div', { cls: 'tooltip-actions' });
const actionsContainer = this.tooltip.createEl('div', { cls: 'kga-tooltip-actions' });
if (isMobile) actionsContainer.classList.add('kga-mobile');
if (isPhone) actionsContainer.classList.add('kga-mobile-phone');
// 정보 텍스트 - 아이폰 최적화
const infoText = actionsContainer.createEl('span', {
text: isMobile ? (isPhone ? '개별 수정' : '개별 클릭 수정') : '개별 클릭으로 하나씩 수정',
cls: 'info-text'
cls: 'kga-info-text'
});
if (isMobile) infoText.classList.add('kga-mobile');
if (isPhone) infoText.classList.add('kga-mobile-phone');
// 액션 버튼들 컨테이너 - 아이폰 최적화
const actionButtons = actionsContainer.createEl('div', { cls: 'action-buttons' });
const actionButtons = actionsContainer.createEl('div', { cls: 'kga-action-buttons' });
if (isMobile) actionButtons.classList.add('kga-mobile');
if (isPhone) actionButtons.classList.add('kga-mobile-phone');
// ❌ 병합된 오류 전체 무시 버튼 - 체크박스와 일관된 스타일
const ignoreAllButton = actionButtons.createEl('button', { cls: 'ignore-all-button' });
const ignoreAllButton = actionButtons.createEl('button', { cls: 'kga-ignore-all-button' });
ignoreAllButton.textContent = '✕'; // X 표시
ignoreAllButton.title = '이 오류들 모두 무시';
if (isMobile) ignoreAllButton.classList.add('kga-mobile');
@ -875,7 +871,7 @@ export class InlineTooltip {
// 모든 수정 적용 버튼 - 녹색 체크로 변경
const applyAllButton = actionButtons.createEl('button', {
text: '✓',
cls: 'apply-all-button'
cls: 'kga-apply-all-button'
});
applyAllButton.title = '모든 수정 사항 적용';
if (isMobile) applyAllButton.classList.add('kga-mobile');
@ -1042,7 +1038,7 @@ export class InlineTooltip {
// 헤더 텍스트 - 컴팩트
const headerText = header.createEl('span', {
text: '맞춤법 오류',
cls: 'header-text kga-single'
cls: 'kga-header-text kga-single'
});
if (isMobile) headerText.classList.add('kga-mobile');
if (isPhone) headerText.classList.add('kga-mobile-phone');
@ -1050,7 +1046,7 @@ export class InlineTooltip {
// 우상단 닫기 버튼 (✕) - 더 작게
const headerCloseButton = header.createEl('button', {
text: '✕',
cls: 'header-close-button kga-single'
cls: 'kga-header-close-button kga-single'
});
if (isMobile) headerCloseButton.classList.add('kga-mobile');
if (isPhone) headerCloseButton.classList.add('kga-mobile-phone');
@ -1077,16 +1073,16 @@ export class InlineTooltip {
});
// 상단 메인 콘텐츠 영역 - 컴팩트한 패딩
const mainContent = this.tooltip.createEl('div', { cls: 'tooltip-main-content' });
const mainContent = this.tooltip.createEl('div', { cls: 'kga-tooltip-main-content' });
if (isMobile) mainContent.classList.add('kga-mobile');
if (isPhone) mainContent.classList.add('kga-mobile-phone');
// 오류 단어 표시 (간소화) - 모바일 최적화 + 형태소 정보
const errorWordContainer = mainContent.createEl('div', { cls: 'error-word-container' });
const errorWordContainer = mainContent.createEl('div', { cls: 'kga-error-word-container' });
const errorWord = errorWordContainer.createEl('span', {
text: error.correction.original,
cls: 'error-word'
cls: 'kga-error-word'
});
// 🎨 AI 상태에 따른 색상 및 스타일 설정 (CSS 클래스로 적용)
@ -1107,7 +1103,7 @@ export class InlineTooltip {
if (error.morphemeInfo && this.isImportantPos(error.morphemeInfo.mainPos, error.morphemeInfo.tags)) {
const posInfo = errorWordContainer.createEl('span', {
text: error.morphemeInfo.mainPos,
cls: 'pos-info'
cls: 'kga-pos-info'
});
if (isMobile) {
posInfo.classList.add('kga-mobile');
@ -1124,7 +1120,7 @@ export class InlineTooltip {
}
// 수정 제안들을 가로로 나열 - 모바일 최적화
const suggestionsList = mainContent.createEl('div', { cls: 'suggestions-list' });
const suggestionsList = mainContent.createEl('div', { cls: 'kga-suggestions-list' });
if (isMobile) {
suggestionsList.classList.add('kga-mobile');
}
@ -1139,7 +1135,7 @@ export class InlineTooltip {
uniqueSuggestions.forEach((suggestion, index) => {
const suggestionButton = suggestionsList.createEl('span', {
text: suggestion,
cls: 'suggestion-button'
cls: 'kga-suggestion-button'
});
if (isMobile) {
@ -1175,7 +1171,7 @@ export class InlineTooltip {
});
// 액션 영역 (아이폰 최적화) - 메인 콘텐츠 내부로 이동
const actionsContainer = mainContent.createEl('div', { cls: 'actions-container' });
const actionsContainer = mainContent.createEl('div', { cls: 'kga-actions-container' });
if (isMobile) {
actionsContainer.classList.add('kga-mobile');
}
@ -1184,7 +1180,7 @@ export class InlineTooltip {
}
// 📚 예외 단어 추가 버튼 (책 아이콘) - 모바일 최적화
const exceptionButton = actionsContainer.createEl('button', { cls: 'exception-button' });
const exceptionButton = actionsContainer.createEl('button', { cls: 'kga-exception-button' });
exceptionButton.textContent = '📚'; // 책 아이콘
exceptionButton.title = '예외 단어로 추가';
@ -1218,7 +1214,7 @@ export class InlineTooltip {
});
// ❌ 오류 무시 버튼 (일시적 무시) - 모바일 최적화
const ignoreButton = actionsContainer.createEl('button', { cls: 'ignore-button' });
const ignoreButton = actionsContainer.createEl('button', { cls: 'kga-ignore-button' });
ignoreButton.textContent = '❌'; // X 표시
ignoreButton.title = '이 오류 무시 (일시적)';
@ -1258,7 +1254,7 @@ export class InlineTooltip {
this.createInlineHelpIcon(error.correction.help, actionsContainer, () => {
if (!helpArea) {
// 도움말 영역 생성
helpArea = this.tooltip!.createEl('div', { cls: 'tooltip-help-area' });
helpArea = this.tooltip!.createEl('div', { cls: 'kga-tooltip-help-area' });
if (isMobile) {
helpArea.classList.add('kga-mobile');
}
@ -1276,7 +1272,7 @@ export class InlineTooltip {
// 🤖 AI 분석 결과 영역 (도움말 영역 아래)
if (error.aiAnalysis) {
const aiArea = this.tooltip!.createEl('div', { cls: 'tooltip-ai-area' });
const aiArea = this.tooltip!.createEl('div', { cls: 'kga-tooltip-ai-area' });
if (isMobile) {
aiArea.classList.add('kga-mobile');
}
@ -1285,10 +1281,10 @@ export class InlineTooltip {
}
// 🤖 AI 아이콘
const aiIcon = aiArea.createEl('span', { text: '🤖', cls: 'ai-icon' });
const aiIcon = aiArea.createEl('span', { text: '🤖', cls: 'kga-ai-icon' });
// AI 추천 이유 간단 표시
const reasoningText = aiArea.createEl('span', { cls: 'ai-reasoning' });
const reasoningText = aiArea.createEl('span', { cls: 'kga-ai-reasoning' });
// AI 분석 이유를 짧게 표시 (첫 번째 문장만)
if (error.aiAnalysis.reasoning) {
@ -1368,24 +1364,18 @@ export class InlineTooltip {
const word = error.correction.original;
try {
// InlineModeService의 새로운 메서드로 동일 단어 모든 오류 제거
if ((window as InlineTooltipWindow).InlineModeService) {
const removedCount = await (window as InlineTooltipWindow).InlineModeService!.addWordToIgnoreListAndRemoveErrors(word);
if (removedCount > 0) {
Logger.log(`📚 예외 단어 추가 및 ${removedCount}개 오류 제거: "${word}"`);
new Notice(`"${word}"를 예외 단어로 추가했습니다. (${removedCount}개 오류 제거)`);
} else {
new Notice(`"${word}"는 이미 예외 단어로 등록되어 있습니다.`);
}
// 툴팁 숨김
this.hide(true); // 강제 닫기
// InlineModeService의 메서드로 동일 단어 모든 오류 제거
const removedCount = await InlineModeService.addWordToIgnoreListAndRemoveErrors(word);
if (removedCount > 0) {
Logger.log(`📚 예외 단어 추가 및 ${removedCount}개 오류 제거: "${word}"`);
new Notice(`"${word}"를 예외 단어로 추가했습니다. (${removedCount}개 오류 제거)`);
} else {
Logger.error('InlineModeService를 찾을 수 없습니다.');
new Notice('예외 단어 추가에 실패했습니다.');
new Notice(`"${word}"는 이미 예외 단어로 등록되어 있습니다.`);
}
// 툴팁 숨김
this.hide(true); // 강제 닫기
} catch (error) {
Logger.error('예외 단어 추가 중 오류:', error);
new Notice('예외 단어 추가에 실패했습니다.');
@ -1400,10 +1390,8 @@ export class InlineTooltip {
Logger.log(`❌ 오류 무시: "${error.correction.original}"`);
// 현재 오류 제거 (InlineModeService를 통해)
if ((window as InlineTooltipWindow).InlineModeService) {
(window as InlineTooltipWindow).InlineModeService!.removeError(null, error.uniqueId);
Logger.debug(`✅ 일시적 무시로 인한 오류 제거: ${error.uniqueId}`);
}
InlineModeService.removeError(null, error.uniqueId);
Logger.debug(`✅ 일시적 무시로 인한 오류 제거: ${error.uniqueId}`);
// 툴팁 숨김
this.hide(true); // 강제 닫기
@ -1461,7 +1449,7 @@ export class InlineTooltip {
* (Inline ) -
*/
private createInlineHelpIcon(helpText: string, container: HTMLElement, onIconClick: () => void): void {
const helpIcon = container.createEl('span', { text: '?', cls: 'help-icon' });
const helpIcon = container.createEl('span', { text: '?', cls: 'kga-help-icon' });
// 모바일 감지 (메서드 내에서 사용)
const isMobile = Platform.isMobile;

View file

@ -81,11 +81,6 @@ export class ModernSettingsTab extends PluginSettingTab {
private createHeader(containerEl: HTMLElement): void {
const header = containerEl.createEl('div', { cls: 'ksc-header' });
const titleSetting = new Setting(header)
.setName('📝 한국어 맞춤법 도우미')
.setHeading();
titleSetting.settingEl.addClasses(['ksc-header-title']);
header.createEl('p', {
text: '플러그인 동작을 커스터마이징하고 AI 기능을 설정하세요',
cls: 'ksc-header-subtitle'

View file

@ -211,11 +211,11 @@ export class VirtualScroller {
*/
private initializeStructure(): void {
clearElement(this.container);
this.container.className = 'virtual-scroller-container';
this.container.className = 'kga-virtual-scroller-container';
// 뷰포트 (스크롤 가능한 영역)
this.viewport = this.container.createEl('div', {
cls: 'virtual-scroller-viewport',
cls: 'kga-virtual-scroller-viewport',
attr: {
style: `
height: ${this.config.containerHeight}px;
@ -228,7 +228,7 @@ export class VirtualScroller {
// 콘텐츠 영역 (전체 높이를 가진 스크롤 영역)
this.content = this.viewport.createEl('div', {
cls: 'virtual-scroller-content',
cls: 'kga-virtual-scroller-content',
attr: {
style: `
position: relative;
@ -354,7 +354,7 @@ export class VirtualScroller {
*/
private createItemElement(item: VirtualItem, index: number): HTMLElement {
const element = document.createElement('div');
element.className = 'virtual-scroller-item';
element.className = 'kga-virtual-scroller-item';
element.dataset.virtualItemId = item.id;
this.updateItemPosition(element, index);

View file

@ -82,7 +82,7 @@ export class TokenWarningModal {
// 확인 모달 표시
return new Promise((resolve) => {
const modal = document.createElement('div');
modal.className = 'modal-overlay token-warning-overlay korean-grammar-token-modal';
modal.className = 'modal-overlay kga-token-warning-overlay korean-grammar-token-modal';
const modalContent = this.createTokenWarningModal(tokenUsage, isOverMaxTokens, maxTokens);
modal.appendChild(modalContent);
@ -192,94 +192,94 @@ export class TokenWarningModal {
*/
private static createTokenWarningModal(tokenUsage: TokenUsage, isOverMaxTokens: boolean, maxTokens: number): HTMLElement {
const content = document.createElement('div');
content.className = 'token-warning-content';
content.className = 'kga-token-warning-content';
const headerInfo = content.appendChild(document.createElement('div'));
headerInfo.className = 'token-warning-header';
headerInfo.className = 'kga-token-warning-header';
const title = headerInfo.appendChild(document.createElement('h3'));
title.className = 'token-warning-title';
title.className = 'kga-token-warning-title';
title.textContent = isOverMaxTokens ? '토큰 사용량 확인' : '토큰 사용량 안내';
const description = headerInfo.appendChild(document.createElement('p'));
description.className = 'token-warning-description';
description.className = 'kga-token-warning-description';
description.textContent = isOverMaxTokens ? '설정된 한계를 초과했습니다' : '예상 사용량이 높습니다';
const details = content.appendChild(document.createElement('div'));
details.className = 'token-warning-details';
details.className = 'kga-token-warning-details';
const stats = details.appendChild(document.createElement('div'));
stats.className = 'token-warning-stats';
stats.className = 'kga-token-warning-stats';
const totalTokenItem = stats.appendChild(document.createElement('div'));
totalTokenItem.className = 'token-stat-item';
totalTokenItem.className = 'kga-token-stat-item';
const totalTokenNumber = totalTokenItem.appendChild(document.createElement('div'));
totalTokenNumber.className = 'token-stat-number';
totalTokenNumber.className = 'kga-token-stat-number';
totalTokenNumber.textContent = tokenUsage.totalEstimated.toLocaleString();
const totalTokenLabel = totalTokenItem.appendChild(document.createElement('div'));
totalTokenLabel.className = 'token-stat-label';
totalTokenLabel.className = 'kga-token-stat-label';
totalTokenLabel.textContent = '총 토큰';
const costItem = stats.appendChild(document.createElement('div'));
costItem.className = 'token-stat-item';
costItem.className = 'kga-token-stat-item';
const costNumber = costItem.appendChild(document.createElement('div'));
costNumber.className = 'token-stat-number';
costNumber.className = 'kga-token-stat-number';
costNumber.textContent = tokenUsage.estimatedCost;
const costLabel = costItem.appendChild(document.createElement('div'));
costLabel.className = 'token-stat-label';
costLabel.className = 'kga-token-stat-label';
costLabel.textContent = '예상 비용';
const rec = details.appendChild(document.createElement('div'));
rec.className = 'token-warning-recommendation';
rec.className = 'kga-token-warning-recommendation';
const recText = rec.appendChild(document.createElement('div'));
recText.className = 'token-warning-recommendation-text';
recText.className = 'kga-token-warning-recommendation-text';
recText.textContent = `입력: ${tokenUsage.inputTokens.toLocaleString()} • 출력: ${tokenUsage.estimatedOutputTokens.toLocaleString()}`;
if (isOverMaxTokens) {
const overLimit = content.appendChild(document.createElement('div'));
overLimit.className = 'token-warning-over-limit';
overLimit.className = 'kga-token-warning-over-limit';
const overLimitIcon = overLimit.appendChild(document.createElement('div'));
overLimitIcon.className = 'token-warning-over-limit-icon';
overLimitIcon.className = 'kga-token-warning-over-limit-icon';
overLimitIcon.textContent = '⚠️';
const overLimitText = overLimit.appendChild(document.createElement('div'));
overLimitText.className = 'token-warning-over-limit-text';
overLimitText.className = 'kga-token-warning-over-limit-text';
const overLimitTitle = overLimitText.appendChild(document.createElement('div'));
overLimitTitle.className = 'token-warning-over-limit-title';
overLimitTitle.className = 'kga-token-warning-over-limit-title';
overLimitTitle.textContent = '설정된 최대 토큰을 초과했습니다';
const overLimitDesc = overLimitText.appendChild(document.createElement('div'));
overLimitDesc.className = 'token-warning-over-limit-description';
overLimitDesc.className = 'kga-token-warning-over-limit-description';
overLimitDesc.textContent = `현재 설정: ${maxTokens.toLocaleString()} 토큰 → 초과량: ${(tokenUsage.totalEstimated - maxTokens).toLocaleString()} 토큰`;
}
const actions = content.appendChild(document.createElement('div'));
actions.className = 'token-warning-actions';
actions.className = 'kga-token-warning-actions';
const cancelBtn = document.createElement('button');
cancelBtn.id = 'token-warning-cancel';
cancelBtn.className = 'token-warning-btn token-warning-btn-cancel';
cancelBtn.className = 'kga-token-warning-btn kga-token-warning-btn-cancel';
cancelBtn.textContent = '취소';
actions.appendChild(cancelBtn);
if (isOverMaxTokens) {
const updateBtn = document.createElement('button');
updateBtn.id = 'token-warning-update-settings';
updateBtn.className = 'token-warning-btn token-warning-btn-settings';
updateBtn.className = 'kga-token-warning-btn kga-token-warning-btn-settings';
updateBtn.textContent = '설정 업데이트 후 진행';
actions.appendChild(updateBtn);
}
const proceedBtn = document.createElement('button');
proceedBtn.id = 'token-warning-proceed';
proceedBtn.className = 'token-warning-btn token-warning-btn-proceed';
proceedBtn.className = 'kga-token-warning-btn kga-token-warning-btn-proceed';
proceedBtn.textContent = isOverMaxTokens ? '강제 진행' : '계속 진행';
actions.appendChild(proceedBtn);

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
/* styles/ai.css */
#correctionPopup .ai-analysis-result {
#correctionPopup .kga-ai-analysis-result {
margin-top: 6px !important;
padding: 6px 8px !important;
background: rgba(var(--color-blue-rgb), 0.1) !important;
@ -9,7 +9,7 @@
font-size: 10px !important;
}
#correctionPopup .ai-confidence {
#correctionPopup .kga-ai-confidence {
display: flex !important;
align-items: center !important;
gap: 4px !important;
@ -18,7 +18,7 @@
color: var(--color-blue) !important;
}
#correctionPopup .confidence-score {
#correctionPopup .kga-confidence-score {
background: var(--color-blue) !important;
color: white !important;
padding: 1px 4px !important;
@ -27,14 +27,14 @@
font-weight: 700 !important;
}
#correctionPopup .ai-reasoning {
#correctionPopup .kga-ai-reasoning {
color: var(--text-normal) !important;
line-height: 1.3 !important;
font-style: italic !important;
opacity: 0.9 !important;
}
#correctionPopup .ai-analyze-btn {
#correctionPopup .kga-ai-analyze-btn {
padding: 6px 12px !important;
border: 1px solid var(--color-blue) !important;
border-radius: 6px !important;
@ -47,18 +47,18 @@
white-space: nowrap !important;
}
#correctionPopup .ai-analyze-btn:hover:not(:disabled) {
#correctionPopup .kga-ai-analyze-btn:hover:not(:disabled) {
background: var(--color-blue) !important;
color: white !important;
}
#correctionPopup .ai-analyze-btn:disabled {
#correctionPopup .kga-ai-analyze-btn:disabled {
opacity: 0.6 !important;
cursor: not-allowed !important;
}
/* AI 서비스 비활성화 상태 */
#correctionPopup .ai-analyze-btn.ai-disabled {
#correctionPopup .kga-ai-analyze-btn.kga-ai-disabled {
border-color: var(--text-muted) !important;
background: rgba(var(--text-muted-rgb), 0.1) !important;
color: var(--text-muted) !important;
@ -67,7 +67,7 @@
}
/* AI 서비스 비활성화 상태 호버 효과 제거 */
#correctionPopup .ai-analyze-btn.ai-disabled:hover {
#correctionPopup .kga-ai-analyze-btn.kga-ai-disabled:hover {
background: rgba(var(--text-muted-rgb), 0.1) !important;
color: var(--text-muted) !important;
border-color: var(--text-muted) !important;

View file

@ -17,7 +17,7 @@
contain: layout style paint;
}
#correctionPopup .popup-overlay {
#correctionPopup .kga-popup-overlay {
position: absolute;
top: 0;
left: 0;
@ -35,7 +35,7 @@
-webkit-user-select: none;
}
#correctionPopup .popup-content {
#correctionPopup .kga-popup-content {
position: relative;
width: min(98vw, 1200px);
height: 95vh;
@ -54,7 +54,7 @@
z-index: 10000;
}
#correctionPopup .content {
#correctionPopup .kga-content {
padding: 20px 24px 0;
display: flex;
flex-direction: column;
@ -64,20 +64,20 @@
}
/* Scrollbar styling */
#correctionPopup .content::-webkit-scrollbar {
#correctionPopup .kga-content::-webkit-scrollbar {
width: 6px;
}
#correctionPopup .content::-webkit-scrollbar-track {
#correctionPopup .kga-content::-webkit-scrollbar-track {
background: var(--background-secondary);
}
#correctionPopup .content::-webkit-scrollbar-thumb {
#correctionPopup .kga-content::-webkit-scrollbar-thumb {
background: var(--background-modifier-border);
border-radius: 3px;
}
#correctionPopup .content::-webkit-scrollbar-thumb:hover {
#correctionPopup .kga-content::-webkit-scrollbar-thumb:hover {
background: var(--background-modifier-border-hover);
}

View file

@ -12,17 +12,17 @@
background: rgba(var(--color-red-rgb), 0.2);
}
.clickable-error {
.kga-clickable-error {
cursor: pointer;
position: relative;
}
.clickable-error:hover {
.kga-clickable-error:hover {
background: rgba(var(--color-red-rgb), 0.25);
border-bottom: 2px solid var(--color-red);
}
.clickable-error:active {
.kga-clickable-error:active {
background: rgba(var(--color-red-rgb), 0.3);
}
@ -34,17 +34,17 @@
font-weight: 500;
}
.spell-corrected.clickable-error {
.spell-corrected.kga-clickable-error {
cursor: pointer;
position: relative;
}
.spell-corrected.clickable-error:hover {
.spell-corrected.kga-clickable-error:hover {
background: rgba(var(--color-green-rgb), 0.25);
border-bottom: 2px solid var(--color-green);
}
.spell-corrected.clickable-error:active {
.spell-corrected.kga-clickable-error:active {
background: rgba(var(--color-green-rgb), 0.3);
}
@ -56,17 +56,17 @@
font-weight: 500;
}
.spell-exception-processed.clickable-error {
.spell-exception-processed.kga-clickable-error {
cursor: pointer;
position: relative;
}
.spell-exception-processed.clickable-error:hover {
.spell-exception-processed.kga-clickable-error:hover {
background: rgba(var(--color-blue-rgb), 0.25);
border-bottom: 2px solid var(--color-blue);
}
.spell-exception-processed.clickable-error:active {
.spell-exception-processed.kga-clickable-error:active {
background: rgba(var(--color-blue-rgb), 0.3);
}
@ -78,17 +78,17 @@
font-weight: 500;
}
.spell-original-kept.clickable-error {
.spell-original-kept.kga-clickable-error {
cursor: pointer;
position: relative;
}
.spell-original-kept.clickable-error:hover {
.spell-original-kept.kga-clickable-error:hover {
background: rgba(255, 165, 0, 0.25);
border-bottom: 2px solid #e67e00;
}
.spell-original-kept.clickable-error:active {
.spell-original-kept.kga-clickable-error:active {
background: rgba(255, 165, 0, 0.35);
}
@ -100,17 +100,17 @@
font-weight: 500;
}
.spell-user-edited.clickable-error {
.spell-user-edited.kga-clickable-error {
cursor: pointer;
position: relative;
}
.spell-user-edited.clickable-error:hover {
.spell-user-edited.kga-clickable-error:hover {
background: rgba(139, 92, 246, 0.25);
border-bottom: 2px solid #8b5cf6;
}
.spell-user-edited.clickable-error:active {
.spell-user-edited.kga-clickable-error:active {
background: rgba(139, 92, 246, 0.35);
}

View file

@ -1,6 +1,6 @@
/* styles/error-summary.css */
#correctionPopup .error-summary {
#correctionPopup .kga-error-summary {
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid var(--background-modifier-border);
@ -14,21 +14,21 @@
flex: 1 1 auto !important;
}
#correctionPopup .error-summary.collapsed .error-summary-content {
#correctionPopup .kga-error-summary.collapsed .kga-error-summary-content {
height: 0;
overflow: hidden;
opacity: 0;
transition: all 0.3s ease;
}
#correctionPopup .error-summary:not(.collapsed) .error-summary-content {
#correctionPopup .kga-error-summary:not(.collapsed) .kga-error-summary-content {
height: 280px;
overflow-y: auto;
opacity: 1;
transition: all 0.3s ease;
}
#correctionPopup .error-summary-toggle {
#correctionPopup .kga-error-summary-toggle {
display: flex;
align-items: center;
justify-content: space-between;
@ -38,20 +38,20 @@
user-select: none;
}
#correctionPopup .error-summary-toggle .left-section {
#correctionPopup .kga-error-summary-toggle .kga-left-section {
display: flex;
align-items: center;
gap: 8px;
}
#correctionPopup .error-summary-toggle:hover {
#correctionPopup .kga-error-summary-toggle:hover {
background: var(--background-modifier-hover);
border-radius: 4px;
margin: 0 -8px 12px -8px;
padding: 8px;
}
#correctionPopup .error-summary-label {
#correctionPopup .kga-error-summary-label {
font-size: 12px;
font-weight: 600;
color: var(--text-muted);
@ -59,7 +59,7 @@
letter-spacing: 0.5px;
}
#correctionPopup .error-count-badge {
#correctionPopup .kga-error-count-badge {
display: inline-flex;
align-items: center;
justify-content: center;
@ -74,36 +74,36 @@
line-height: 1;
}
#correctionPopup .toggle-icon {
#correctionPopup .kga-toggle-icon {
font-size: 12px;
color: var(--text-muted);
transition: transform 0.2s ease;
}
#correctionPopup .error-summary-content {
#correctionPopup .kga-error-summary-content {
scrollbar-width: thin;
-webkit-overflow-scrolling: touch;
}
#correctionPopup .error-summary-content::-webkit-scrollbar {
#correctionPopup .kga-error-summary-content::-webkit-scrollbar {
width: 6px;
}
#correctionPopup .error-summary-content::-webkit-scrollbar-track {
#correctionPopup .kga-error-summary-content::-webkit-scrollbar-track {
background: var(--background-secondary);
}
#correctionPopup .error-summary-content::-webkit-scrollbar-thumb {
#correctionPopup .kga-error-summary-content::-webkit-scrollbar-thumb {
background: var(--background-modifier-border);
border-radius: 3px;
}
#correctionPopup .error-summary-content::-webkit-scrollbar-thumb:hover {
#correctionPopup .kga-error-summary-content::-webkit-scrollbar-thumb:hover {
background: var(--background-modifier-border-hover);
}
/* Improved compact error item styles */
#correctionPopup .error-item-compact {
#correctionPopup .kga-error-item-compact {
margin-bottom: 4px !important;
padding: 6px 8px !important;
background: var(--background-secondary-alt) !important;
@ -114,18 +114,18 @@
line-height: 1.3 !important;
}
#correctionPopup .error-item-compact:hover {
#correctionPopup .kga-error-item-compact:hover {
background: var(--background-modifier-hover) !important;
}
#correctionPopup .error-row {
#correctionPopup .kga-error-row {
display: flex !important;
align-items: center !important;
gap: 8px !important;
margin-bottom: 3px !important;
}
#correctionPopup .error-original-compact {
#correctionPopup .kga-error-original-compact {
min-width: 60px !important;
font-weight: 600 !important;
color: var(--color-red) !important;
@ -139,31 +139,31 @@
}
/* 수정된 상태의 원본 텍스트 스타일 */
#correctionPopup .error-original-compact.corrected {
#correctionPopup .kga-error-original-compact.corrected {
color: var(--color-green) !important;
background: rgba(var(--color-green-rgb), 0.15) !important;
}
/* 예외처리된 상태의 원본 텍스트 스타일 */
#correctionPopup .error-original-compact.exception-processed {
#correctionPopup .kga-error-original-compact.exception-processed {
color: var(--color-blue) !important;
background: rgba(var(--color-blue-rgb), 0.15) !important;
}
/* 원본 유지된 상태의 원본 텍스트 스타일 */
#correctionPopup .error-original-compact.original-kept {
#correctionPopup .kga-error-original-compact.original-kept {
color: var(--color-orange) !important;
background: rgba(var(--color-orange-rgb), 0.15) !important;
}
/* 사용자 편집된 상태의 원본 텍스트 스타일 */
#correctionPopup .error-original-compact.user-edited {
#correctionPopup .kga-error-original-compact.user-edited {
color: #8b5cf6 !important;
background: rgba(139, 92, 246, 0.15) !important;
}
/* 편집 모드 input 스타일 */
#correctionPopup .error-original-input {
#correctionPopup .kga-error-original-input {
min-width: 60px !important;
font-weight: 600 !important;
color: #8b5cf6 !important;
@ -180,7 +180,7 @@
/* 모바일 편집 모드 개선 */
@media (max-width: 768px) {
#correctionPopup .error-original-input {
#correctionPopup .kga-error-original-input {
min-width: 80px !important;
font-size: 14px !important;
padding: 6px 8px !important;
@ -189,7 +189,7 @@
}
/* 모바일 편집 버튼 컨테이너 */
#correctionPopup .mobile-edit-container {
#correctionPopup .kga-mobile-edit-container {
display: flex !important;
align-items: center !important;
gap: 4px !important;
@ -198,14 +198,14 @@
}
/* 편집 모드일 때 input이 최대한 공간 차지 */
#correctionPopup .mobile-edit-container .error-original-input {
#correctionPopup .kga-mobile-edit-container .kga-error-original-input {
flex: 1 !important;
max-width: none !important;
min-width: auto !important;
}
/* 모바일 편집 버튼 스타일 */
#correctionPopup .mobile-edit-btn {
#correctionPopup .kga-mobile-edit-btn {
padding: 6px 8px !important;
font-size: 12px !important;
border: none !important;
@ -221,32 +221,32 @@
justify-content: center !important;
}
#correctionPopup .mobile-edit-btn.save {
#correctionPopup .kga-mobile-edit-btn.save {
background: #10b981 !important;
color: white !important;
}
#correctionPopup .mobile-edit-btn.cancel {
#correctionPopup .kga-mobile-edit-btn.cancel {
background: #ef4444 !important;
color: white !important;
}
#correctionPopup .mobile-edit-btn:hover {
#correctionPopup .kga-mobile-edit-btn:hover {
opacity: 0.8 !important;
}
#correctionPopup .mobile-edit-btn:active {
#correctionPopup .kga-mobile-edit-btn:active {
transform: scale(0.95) !important;
}
/* 모바일 편집 모드일 때 수정 제안 및 예외 처리 버튼 강제 숨김 */
@media (max-width: 768px) {
#correctionPopup .mobile-edit-container ~ .error-suggestions-compact,
#correctionPopup .mobile-edit-container ~ .error-exception-btn,
#correctionPopup .error-card:has(.mobile-edit-container) .error-suggestions-compact,
#correctionPopup .error-card:has(.mobile-edit-container) .error-exception-btn,
#correctionPopup .error-card:has(.mobile-edit-container) .suggestion-compact,
#correctionPopup .error-card:has(.mobile-edit-container) .keep-original {
#correctionPopup .kga-mobile-edit-container ~ .kga-error-suggestions-compact,
#correctionPopup .kga-mobile-edit-container ~ .kga-error-exception-btn,
#correctionPopup .kga-error-card:has(.kga-mobile-edit-container) .kga-error-suggestions-compact,
#correctionPopup .kga-error-card:has(.kga-mobile-edit-container) .kga-error-exception-btn,
#correctionPopup .kga-error-card:has(.kga-mobile-edit-container) .kga-suggestion-compact,
#correctionPopup .kga-error-card:has(.kga-mobile-edit-container) .kga-keep-original {
display: none !important;
visibility: hidden !important;
opacity: 0 !important;
@ -259,32 +259,32 @@
}
/* 편집 모드일 때 수정 제안 및 예외 처리 버튼 숨기기 (폴백) */
#correctionPopup .error-card.editing-mode .error-suggestions-compact,
#correctionPopup .error-card.editing-mode .error-exception-btn,
#correctionPopup .error-card.editing-mode .suggestion-compact,
#correctionPopup .error-card.editing-mode .keep-original {
#correctionPopup .kga-error-card.kga-editing-mode .kga-error-suggestions-compact,
#correctionPopup .kga-error-card.kga-editing-mode .kga-error-exception-btn,
#correctionPopup .kga-error-card.kga-editing-mode .kga-suggestion-compact,
#correctionPopup .kga-error-card.kga-editing-mode .kga-keep-original {
display: none !important;
visibility: hidden !important;
opacity: 0 !important;
}
/* 편집 모드일 때 카드 레이아웃 조정 */
#correctionPopup .error-card.editing-mode {
#correctionPopup .kga-error-card.kga-editing-mode {
padding: 8px !important;
}
#correctionPopup .error-card.editing-mode .error-content-compact {
#correctionPopup .kga-error-card.kga-editing-mode .kga-error-content-compact {
gap: 4px !important;
}
#correctionPopup .error-suggestions-compact {
#correctionPopup .kga-error-suggestions-compact {
display: flex !important;
flex-wrap: wrap !important;
gap: 4px !important;
flex: 1 !important;
}
#correctionPopup .suggestion-compact {
#correctionPopup .kga-suggestion-compact {
background: var(--background-primary) !important;
border: 1px solid var(--background-modifier-border) !important;
border-radius: 8px !important;
@ -297,30 +297,30 @@
font-weight: 500 !important;
}
#correctionPopup .suggestion-compact:hover {
#correctionPopup .kga-suggestion-compact:hover {
background: var(--interactive-hover) !important;
border-color: var(--interactive-accent) !important;
}
#correctionPopup .suggestion-compact.selected {
#correctionPopup .kga-suggestion-compact.selected {
background: var(--interactive-accent) !important;
color: var(--text-on-accent) !important;
border-color: var(--interactive-accent) !important;
}
#correctionPopup .suggestion-compact.keep-original {
#correctionPopup .kga-suggestion-compact.kga-keep-original {
background: var(--background-modifier-hover) !important;
color: var(--text-muted) !important;
font-size: 10px !important;
font-weight: 400 !important;
}
#correctionPopup .suggestion-compact.keep-original.selected {
#correctionPopup .kga-suggestion-compact.kga-keep-original.selected {
background: var(--text-muted) !important;
color: var(--background-primary) !important;
}
#correctionPopup .error-help-compact {
#correctionPopup .kga-error-help-compact {
padding: 4px 8px !important;
border-top: 1px solid var(--background-modifier-border) !important;
background: var(--background-secondary) !important;
@ -335,7 +335,7 @@
}
/* Error placeholder styles */
#correctionPopup .error-placeholder {
#correctionPopup .kga-error-placeholder {
display: flex;
flex-direction: column;
align-items: center;
@ -348,20 +348,20 @@
border: 1px dashed var(--background-modifier-border);
}
#correctionPopup .placeholder-icon {
#correctionPopup .kga-placeholder-icon {
font-size: 32px;
color: var(--color-green);
margin-bottom: 12px;
}
#correctionPopup .placeholder-text {
#correctionPopup .kga-placeholder-text {
font-size: 14px;
font-weight: 500;
color: var(--text-normal);
margin-bottom: 6px;
}
#correctionPopup .placeholder-subtext {
#correctionPopup .kga-placeholder-subtext {
font-size: 12px;
color: var(--text-muted);
opacity: 0.8;

View file

@ -1,6 +1,6 @@
/* styles/header.css */
#correctionPopup .header {
#correctionPopup .kga-header {
padding: 20px 24px 16px;
background: var(--background-primary);
border-bottom: 1px solid var(--background-modifier-border-hover);
@ -10,7 +10,7 @@
flex-shrink: 0;
}
#correctionPopup .header h2 {
#correctionPopup .kga-header h2 {
margin: 0;
font-size: 18px;
font-weight: 600;
@ -19,7 +19,7 @@
text-rendering: optimizeLegibility;
}
#correctionPopup .close-btn-header {
#correctionPopup .kga-close-btn-header {
background: none;
border: none;
font-size: 24px;
@ -35,13 +35,13 @@
transition: all 0.2s ease;
}
#correctionPopup .close-btn-header:hover {
#correctionPopup .kga-close-btn-header:hover {
background: var(--background-modifier-hover);
color: var(--text-normal);
}
#correctionPopup .cancel-btn,
#correctionPopup .apply-btn {
#correctionPopup .kga-cancel-btn,
#correctionPopup .kga-apply-btn {
padding: 8px 16px;
border: none;
border-radius: 8px;
@ -55,30 +55,30 @@
transform: translateZ(0);
}
#correctionPopup .cancel-btn {
#correctionPopup .kga-cancel-btn {
background: var(--background-secondary);
color: var(--text-muted);
}
#correctionPopup .cancel-btn:hover {
#correctionPopup .kga-cancel-btn:hover {
background: var(--background-modifier-hover);
color: var(--text-normal);
}
#correctionPopup .apply-btn {
#correctionPopup .kga-apply-btn {
background: var(--interactive-accent);
color: var(--text-on-accent);
}
#correctionPopup .header .apply-btn {
#correctionPopup .kga-header .kga-apply-btn {
margin-left: 8px;
}
#correctionPopup .apply-btn:hover {
#correctionPopup .kga-apply-btn:hover {
background: var(--interactive-accent-hover);
}
#correctionPopup .button-area {
#correctionPopup .kga-button-area {
display: flex;
justify-content: flex-end;
gap: 8px;
@ -89,29 +89,29 @@
}
/* === Button State Classes === */
#correctionPopup .header-button-disabled {
#correctionPopup .kga-header-button-disabled {
opacity: 0.6 !important;
cursor: not-allowed !important;
}
#correctionPopup .header-button-enabled {
#correctionPopup .kga-header-button-enabled {
opacity: 1 !important;
cursor: pointer !important;
}
#correctionPopup .header-button-primary-hover {
#correctionPopup .kga-header-button-primary-hover {
background-color: var(--interactive-accent-hover) !important;
}
#correctionPopup .header-button-secondary-hover {
#correctionPopup .kga-header-button-secondary-hover {
background-color: var(--background-modifier-hover) !important;
}
#correctionPopup .header-button-primary {
#correctionPopup .kga-header-button-primary {
background-color: var(--interactive-accent) !important;
}
#correctionPopup .header-button-secondary {
#correctionPopup .kga-header-button-secondary {
background-color: var(--background-primary) !important;
}

View file

@ -72,7 +72,7 @@
2. Header Section
======================================== */
.tooltip-header {
.kga-tooltip-header {
padding: 8px 12px;
border-bottom: 1px solid var(--background-modifier-border);
background: var(--background-secondary);
@ -87,18 +87,18 @@
}
/* Mobile header variants */
.tooltip-header.kga-mobile {
.kga-tooltip-header.kga-mobile {
padding: 11px 13px;
font-size: 12px;
}
.tooltip-header.kga-mobile-phone {
.kga-tooltip-header.kga-mobile-phone {
padding: 10px 12px;
font-size: 11px;
}
/* Single tooltip header */
.tooltip-header.kga-single {
.kga-tooltip-header.kga-single {
padding: 6px 10px;
display: flex;
justify-content: space-between;
@ -109,31 +109,31 @@
min-height: 32px;
}
.tooltip-header.kga-single.kga-mobile-phone {
.kga-tooltip-header.kga-single.kga-mobile-phone {
padding: 6px 10px;
font-size: 12px;
}
.tooltip-header.kga-single.kga-mobile {
.kga-tooltip-header.kga-single.kga-mobile {
padding: 7px 11px;
font-size: 13px;
}
/* Header text */
.header-text {
.kga-header-text {
flex: 1;
text-align: center;
}
.header-text.kga-single {
.kga-header-text.kga-single {
font-size: 13px;
}
.header-text.kga-single.kga-mobile-phone {
.kga-header-text.kga-single.kga-mobile-phone {
font-size: 12px;
}
.header-text.kga-single.kga-mobile {
.kga-header-text.kga-single.kga-mobile {
font-size: 13px;
}
@ -141,7 +141,7 @@
3. Header Close Button
======================================== */
.header-close-button {
.kga-header-close-button {
position: absolute;
right: 8px;
top: 50%;
@ -173,20 +173,20 @@
appearance: none;
}
.header-close-button.kga-mobile {
.kga-header-close-button.kga-mobile {
right: 10px;
font-size: 16px;
touch-action: manipulation;
}
.header-close-button.kga-mobile-phone {
.kga-header-close-button.kga-mobile-phone {
right: 12px;
font-size: 18px;
touch-action: manipulation;
}
/* Single tooltip close button */
.header-close-button.kga-single {
.kga-header-close-button.kga-single {
background: none;
border: none;
color: var(--text-muted);
@ -206,26 +206,26 @@
transform: none;
}
.header-close-button.kga-single.kga-mobile {
.kga-header-close-button.kga-single.kga-mobile {
font-size: 15px;
width: 22px;
height: 22px;
}
.header-close-button.kga-single.kga-mobile-phone {
.kga-header-close-button.kga-single.kga-mobile-phone {
font-size: 14px;
width: 20px;
height: 20px;
}
/* Close button hover states */
.header-close-button:hover {
.kga-header-close-button:hover {
opacity: 1;
color: var(--text-normal);
transform: translateY(-50%) scale(1.2);
}
.header-close-button.kga-single:hover {
.kga-header-close-button.kga-single:hover {
opacity: 1;
color: var(--text-normal);
background: var(--interactive-hover);
@ -235,19 +235,19 @@
4. Scroll Container
======================================== */
.tooltip-scroll-container {
.kga-tooltip-scroll-container {
flex: 1;
overflow-y: auto;
max-height: 250px;
min-height: auto;
}
.tooltip-scroll-container.kga-mobile {
.kga-tooltip-scroll-container.kga-mobile {
max-height: 320px;
min-height: 140px;
}
.tooltip-scroll-container.kga-mobile-phone {
.kga-tooltip-scroll-container.kga-mobile-phone {
max-height: 280px;
min-height: 120px;
}
@ -256,24 +256,24 @@
5. Error Section (Merged Tooltip)
======================================== */
.error-section {
.kga-error-section {
padding: 8px 12px;
}
.error-section.kga-mobile {
.kga-error-section.kga-mobile {
padding: 11px 13px;
}
.error-section.kga-mobile-phone {
.kga-error-section.kga-mobile-phone {
padding: 10px 12px;
}
.error-section.kga-bordered {
.kga-error-section.kga-bordered {
border-top: 1px solid var(--background-modifier-border-hover);
}
/* Error line layout */
.error-line {
.kga-error-line {
display: flex;
align-items: center;
gap: 8px;
@ -281,12 +281,12 @@
min-height: 28px;
}
.error-line.kga-mobile {
.kga-error-line.kga-mobile {
gap: 7px;
min-height: 34px;
}
.error-line.kga-mobile-phone {
.kga-error-line.kga-mobile-phone {
gap: 6px;
min-height: 32px;
}
@ -295,7 +295,7 @@
6. Error Word Display
======================================== */
.error-word {
.kga-error-word {
color: var(--text-error);
font-weight: 600;
background: rgba(255, 0, 0, 0.1);
@ -311,7 +311,7 @@
line-height: 1.2;
}
.error-word.kga-mobile {
.kga-error-word.kga-mobile {
padding: 4px 7px;
font-size: 13px;
min-width: 70px;
@ -319,7 +319,7 @@
line-height: 1.3;
}
.error-word.kga-mobile-phone {
.kga-error-word.kga-mobile-phone {
padding: 3px 6px;
font-size: 12px;
min-width: 70px;
@ -328,21 +328,21 @@
}
/* Error word with AI status */
.error-word.kga-ai-corrected {
.kga-error-word.kga-ai-corrected {
color: #10b981;
background: rgba(16, 185, 129, 0.1);
cursor: pointer;
transition: opacity 0.2s ease;
}
.error-word.kga-ai-exception {
.kga-error-word.kga-ai-exception {
color: #3b82f6;
background: rgba(59, 130, 246, 0.1);
cursor: pointer;
transition: opacity 0.2s ease;
}
.error-word.kga-ai-keep-original {
.kga-error-word.kga-ai-keep-original {
color: #f59e0b;
background: rgba(245, 158, 11, 0.1);
cursor: pointer;
@ -350,7 +350,7 @@
}
/* Error word container */
.error-word-container {
.kga-error-word-container {
display: flex;
flex-direction: column;
align-items: center;
@ -358,7 +358,7 @@
}
/* Part-of-speech info */
.pos-info {
.kga-pos-info {
color: var(--text-accent);
font-size: 10px;
font-weight: 500;
@ -368,23 +368,23 @@
border-radius: 3px;
}
.pos-info.kga-mobile-phone {
.kga-pos-info.kga-mobile-phone {
font-size: 9px;
}
/* Arrow separator */
.error-line > span.kga-arrow {
.kga-error-line > span.kga-arrow {
color: var(--text-muted);
font-weight: bold;
flex-shrink: 0;
font-size: 14px;
}
.error-line > span.kga-arrow.kga-mobile {
.kga-error-line > span.kga-arrow.kga-mobile {
font-size: 13px;
}
.error-line > span.kga-arrow.kga-mobile-phone {
.kga-error-line > span.kga-arrow.kga-mobile-phone {
font-size: 12px;
}
@ -392,7 +392,7 @@
7. Suggestions List
======================================== */
.suggestions-list {
.kga-suggestions-list {
display: flex;
align-items: center;
gap: 4px;
@ -401,16 +401,16 @@
overflow: hidden;
}
.suggestions-list.kga-mobile {
.kga-suggestions-list.kga-mobile {
gap: 4px;
}
.suggestions-list.kga-mobile-phone {
.kga-suggestions-list.kga-mobile-phone {
gap: 3px;
}
/* Suggestion buttons */
.suggestion-button {
.kga-suggestion-button {
color: var(--text-normal);
font-weight: 600;
background: rgba(59, 130, 246, 0.1);
@ -420,19 +420,19 @@
cursor: pointer;
}
.suggestion-button.kga-mobile {
.kga-suggestion-button.kga-mobile {
padding: 4px 7px;
font-size: 13px;
touch-action: manipulation;
}
.suggestion-button.kga-mobile-phone {
.kga-suggestion-button.kga-mobile-phone {
padding: 3px 6px;
font-size: 12px;
touch-action: manipulation;
}
.suggestion-button:hover {
.kga-suggestion-button:hover {
background: rgba(59, 130, 246, 0.15);
}
@ -440,7 +440,7 @@
8. Main Content (Single Tooltip)
======================================== */
.tooltip-main-content {
.kga-tooltip-main-content {
display: flex;
align-items: center;
gap: 6px;
@ -450,12 +450,12 @@
min-height: 0;
}
.tooltip-main-content.kga-mobile {
.kga-tooltip-main-content.kga-mobile {
gap: 5px;
padding: 5px 9px;
}
.tooltip-main-content.kga-mobile-phone {
.kga-tooltip-main-content.kga-mobile-phone {
gap: 4px;
padding: 4px 8px;
}
@ -464,7 +464,7 @@
9. Actions Container
======================================== */
.tooltip-actions {
.kga-tooltip-actions {
display: flex;
align-items: center;
justify-content: space-between;
@ -476,20 +476,20 @@
border-bottom-right-radius: 12px;
}
.tooltip-actions.kga-mobile {
.kga-tooltip-actions.kga-mobile {
padding: 7px 11px 9px 11px;
gap: 7px;
min-height: 44px;
}
.tooltip-actions.kga-mobile-phone {
.kga-tooltip-actions.kga-mobile-phone {
padding: 8px 12px 10px 12px;
gap: 8px;
min-height: 48px;
}
/* Actions container in single tooltip */
.actions-container {
.kga-actions-container {
display: flex;
align-items: center;
gap: 6px;
@ -497,18 +497,18 @@
flex-shrink: 0;
}
.actions-container.kga-mobile {
.kga-actions-container.kga-mobile {
gap: 7px;
min-height: 26px;
}
.actions-container.kga-mobile-phone {
.kga-actions-container.kga-mobile-phone {
gap: 8px;
min-height: 28px;
}
/* Info text */
.info-text {
.kga-info-text {
font-size: 11px;
color: var(--text-muted);
flex: 1;
@ -518,30 +518,30 @@
text-overflow: ellipsis;
}
.info-text.kga-mobile {
.kga-info-text.kga-mobile {
font-size: 12px;
padding-right: 4px;
}
.info-text.kga-mobile-phone {
.kga-info-text.kga-mobile-phone {
font-size: 11px;
padding-right: 4px;
}
/* Action buttons group */
.action-buttons {
.kga-action-buttons {
display: flex;
align-items: center;
gap: 6px;
flex-shrink: 0;
}
.action-buttons.kga-mobile {
.kga-action-buttons.kga-mobile {
gap: 5px;
min-height: 30px;
}
.action-buttons.kga-mobile-phone {
.kga-action-buttons.kga-mobile-phone {
gap: 6px;
min-height: 32px;
}
@ -551,8 +551,8 @@
======================================== */
/* Ignore button (red X) */
.ignore-all-button,
.ignore-button {
.kga-ignore-all-button,
.kga-ignore-button {
background: #ef4444;
color: white;
border: 1px solid #ef4444;
@ -570,8 +570,8 @@
box-shadow: 0 2px 4px rgba(239, 68, 68, 0.2);
}
.ignore-all-button.kga-mobile,
.ignore-button.kga-mobile {
.kga-ignore-all-button.kga-mobile,
.kga-ignore-button.kga-mobile {
border-radius: 6px;
padding: 7px;
font-size: 15px;
@ -581,8 +581,8 @@
touch-action: manipulation;
}
.ignore-all-button.kga-mobile-phone,
.ignore-button.kga-mobile-phone {
.kga-ignore-all-button.kga-mobile-phone,
.kga-ignore-button.kga-mobile-phone {
border-radius: 6px;
padding: 8px;
font-size: 16px;
@ -592,7 +592,7 @@
touch-action: manipulation;
}
.ignore-button.kga-single {
.kga-ignore-button.kga-single {
background: var(--interactive-normal);
border: 1px solid var(--background-modifier-border);
border-radius: 4px;
@ -603,7 +603,7 @@
max-height: none;
}
.ignore-button.kga-single.kga-mobile {
.kga-ignore-button.kga-single.kga-mobile {
border-radius: 5px;
padding: 6px;
min-height: 28px;
@ -611,7 +611,7 @@
max-height: 28px;
}
.ignore-button.kga-single.kga-mobile-phone {
.kga-ignore-button.kga-single.kga-mobile-phone {
border-radius: 5px;
padding: 5px;
font-size: 11px;
@ -620,21 +620,21 @@
max-height: 26px;
}
.ignore-all-button:hover,
.ignore-button:hover {
.kga-ignore-all-button:hover,
.kga-ignore-button:hover {
background: #dc2626;
border-color: #dc2626;
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(239, 68, 68, 0.3);
}
.ignore-button.kga-single:hover {
.kga-ignore-button.kga-single:hover {
background: var(--interactive-hover);
transform: translateY(-1px);
}
/* Apply button (green check) */
.apply-all-button {
.kga-apply-all-button {
background: #10b981;
color: white;
border: 1px solid #10b981;
@ -652,7 +652,7 @@
box-shadow: 0 2px 4px rgba(16, 185, 129, 0.2);
}
.apply-all-button.kga-mobile {
.kga-apply-all-button.kga-mobile {
border-radius: 6px;
padding: 7px;
font-size: 15px;
@ -662,7 +662,7 @@
touch-action: manipulation;
}
.apply-all-button.kga-mobile-phone {
.kga-apply-all-button.kga-mobile-phone {
border-radius: 6px;
padding: 8px;
font-size: 16px;
@ -672,7 +672,7 @@
touch-action: manipulation;
}
.apply-all-button:hover {
.kga-apply-all-button:hover {
background: #059669;
border-color: #059669;
transform: translateY(-1px);
@ -680,7 +680,7 @@
}
/* Exception button (book icon) */
.exception-button {
.kga-exception-button {
background: var(--interactive-normal);
border: 1px solid var(--background-modifier-border);
border-radius: 4px;
@ -694,7 +694,7 @@
line-height: 1;
}
.exception-button.kga-mobile {
.kga-exception-button.kga-mobile {
border-radius: 5px;
padding: 6px;
font-size: 14px;
@ -704,7 +704,7 @@
touch-action: manipulation;
}
.exception-button.kga-mobile-phone {
.kga-exception-button.kga-mobile-phone {
border-radius: 5px;
padding: 5px;
font-size: 13px;
@ -714,7 +714,7 @@
touch-action: manipulation;
}
.exception-button:hover {
.kga-exception-button:hover {
background: var(--interactive-hover);
transform: translateY(-1px);
}
@ -723,14 +723,14 @@
11. Help Area
======================================== */
.help-container {
.kga-help-container {
display: flex;
align-items: center;
margin-left: 4px;
flex-shrink: 0;
}
.tooltip-help-area {
.kga-tooltip-help-area {
padding: 6px 10px;
border-top: 1px solid var(--background-modifier-border);
background: var(--background-secondary);
@ -743,20 +743,20 @@
flex-shrink: 0;
}
.tooltip-help-area.kga-mobile {
.kga-tooltip-help-area.kga-mobile {
padding: 5px 9px;
font-size: 11px;
min-height: 22px;
}
.tooltip-help-area.kga-mobile-phone {
.kga-tooltip-help-area.kga-mobile-phone {
padding: 4px 8px;
font-size: 10px;
min-height: 20px;
}
/* Help icon */
.help-container > span {
.kga-help-container > span {
color: var(--text-muted);
cursor: pointer;
width: 18px;
@ -774,19 +774,19 @@
line-height: 1;
}
.help-container > span.kga-mobile {
.kga-help-container > span.kga-mobile {
width: 18px;
height: 18px;
font-size: 9px;
}
.help-container > span.kga-mobile-phone {
.kga-help-container > span.kga-mobile-phone {
width: 16px;
height: 16px;
font-size: 8px;
}
.help-container > span:hover {
.kga-help-container > span:hover {
background: var(--interactive-hover);
border-color: var(--text-normal);
color: var(--text-normal);
@ -797,7 +797,7 @@
12. AI Analysis Area
======================================== */
.tooltip-ai-area {
.kga-tooltip-ai-area {
padding: 6px 10px;
border-top: 1px solid var(--background-modifier-border);
background: linear-gradient(135deg, rgba(59, 130, 246, 0.05), rgba(16, 185, 129, 0.05));
@ -811,26 +811,26 @@
flex-shrink: 0;
}
.tooltip-ai-area.kga-mobile {
.kga-tooltip-ai-area.kga-mobile {
padding: 5px 9px;
font-size: 11px;
gap: 5px;
min-height: 22px;
}
.tooltip-ai-area.kga-mobile-phone {
.kga-tooltip-ai-area.kga-mobile-phone {
padding: 4px 8px;
font-size: 10px;
gap: 4px;
min-height: 20px;
}
.tooltip-ai-area > span:first-child {
.kga-tooltip-ai-area > span:first-child {
font-size: 12px;
flex-shrink: 0;
}
.tooltip-ai-area > span:last-child {
.kga-tooltip-ai-area > span:last-child {
flex: 1;
font-style: italic;
font-size: 11px;
@ -841,12 +841,12 @@
13. AI Icon & Reasoning Text
======================================== */
.ai-icon {
.kga-ai-icon {
font-size: 12px;
flex-shrink: 0;
}
.ai-reasoning {
.kga-ai-reasoning {
flex: 1;
font-style: italic;
font-size: 11px;
@ -854,7 +854,7 @@
}
/* Help icon - reusable class */
.help-icon {
.kga-help-icon {
color: var(--text-muted);
cursor: pointer;
width: 18px;
@ -872,19 +872,19 @@
line-height: 1;
}
.help-icon.kga-mobile {
.kga-help-icon.kga-mobile {
width: 18px;
height: 18px;
font-size: 9px;
}
.help-icon.kga-mobile-phone {
.kga-help-icon.kga-mobile-phone {
width: 16px;
height: 16px;
font-size: 8px;
}
.help-icon:hover {
.kga-help-icon:hover {
background: var(--interactive-hover);
border-color: var(--text-normal);
color: var(--text-normal);

View file

@ -1,6 +1,6 @@
/* styles/keyboard.css */
.keyboard-focused {
.kga-keyboard-focused {
outline: 2px solid var(--interactive-accent) !important;
outline-offset: 2px !important;
border-radius: 4px !important;
@ -8,13 +8,13 @@
transition: all 0.2s ease !important;
}
.error-item-compact.keyboard-focused {
.kga-error-item-compact.kga-keyboard-focused {
background: var(--background-modifier-hover) !important;
border-left: 3px solid var(--interactive-accent) !important;
padding-left: 13px !important; /* 원래 16px에서 border 3px를 뺀 값 */
}
.keyboard-navigation-hint {
.kga-keyboard-navigation-hint {
position: fixed;
bottom: 20px;
right: 20px;
@ -32,7 +32,7 @@
transition: opacity 0.2s ease;
}
.keyboard-navigation-hint .hint-header {
.kga-keyboard-navigation-hint .kga-hint-header {
display: flex;
justify-content: space-between;
align-items: center;
@ -42,14 +42,14 @@
border-radius: 8px 8px 0 0;
}
.keyboard-navigation-hint .hint-title {
.kga-keyboard-navigation-hint .kga-hint-title {
font-weight: 600;
color: var(--text-normal);
font-size: 12px;
margin: 0;
}
.keyboard-navigation-hint .hint-close-btn {
.kga-keyboard-navigation-hint .kga-hint-close-btn {
background: none;
border: none;
color: var(--text-muted);
@ -67,31 +67,31 @@
justify-content: center;
}
.keyboard-navigation-hint .hint-close-btn:hover {
.kga-keyboard-navigation-hint .kga-hint-close-btn:hover {
background: var(--background-modifier-hover);
color: var(--text-normal);
}
.keyboard-navigation-hint .hint-close-btn:active {
.kga-keyboard-navigation-hint .kga-hint-close-btn:active {
background: var(--background-modifier-active);
}
.keyboard-navigation-hint .hint-item {
.kga-keyboard-navigation-hint .kga-hint-item {
display: flex;
justify-content: space-between;
margin: 2px 0;
padding: 4px 14px;
}
.keyboard-navigation-hint .hint-item:first-of-type {
.kga-keyboard-navigation-hint .kga-hint-item:first-of-type {
padding-top: 8px;
}
.keyboard-navigation-hint .hint-item:last-of-type {
.kga-keyboard-navigation-hint .kga-hint-item:last-of-type {
padding-bottom: 10px;
}
.keyboard-navigation-hint .hint-key {
.kga-keyboard-navigation-hint .kga-hint-key {
font-family: var(--font-monospace);
background: var(--background-modifier-border);
padding: 1px 4px;
@ -101,11 +101,11 @@
text-align: center;
}
.keyboard-navigation-hint .hint-desc {
.kga-keyboard-navigation-hint .kga-hint-desc {
color: var(--text-muted);
}
.token-warning-keyboard-hint {
.kga-token-warning-keyboard-hint {
margin-top: 16px;
padding: 8px 12px;
background: var(--background-modifier-border);
@ -116,7 +116,7 @@
border: 1px solid var(--background-modifier-border);
}
.error-item-highlighted {
.kga-error-item-highlighted {
animation: error-card-highlight 2s ease-out forwards;
position: relative;
z-index: 1;
@ -143,7 +143,7 @@
}
}
.theme-dark .error-item-highlighted {
.theme-dark .kga-error-item-highlighted {
animation: error-card-highlight-dark 2s ease-out forwards;
}
@ -168,7 +168,7 @@
}
}
.edit-completion-highlight {
.kga-edit-completion-highlight {
animation: edit-completion-pulse 2s ease-out forwards;
position: relative;
z-index: 1;
@ -201,7 +201,7 @@
}
}
.theme-dark .edit-completion-highlight {
.theme-dark .kga-edit-completion-highlight {
animation: edit-completion-pulse-dark 2s ease-out forwards;
}

View file

@ -18,13 +18,13 @@
@import url("responsive.css");
/* Additional CSS classes for replacing inline styles that were not component-specific */
.preview-header-top {
.kga-preview-header-top {
display: flex;
align-items: center;
gap: 8px;
}
.pagination-container-hidden {
.kga-pagination-container-hidden {
display: none;
}

View file

@ -2,7 +2,7 @@
/* Mobile Edit Mode Styles */
.mobile-edit-input {
.kga-mobile-edit-input {
width: 100%;
min-width: 200px;
padding: 8px 12px;
@ -15,7 +15,7 @@
outline: none;
}
.mobile-edit-container {
.kga-mobile-edit-container {
position: relative;
display: inline-block;
min-width: 250px;
@ -25,15 +25,15 @@
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.mobile-edit-buttons {
.kga-mobile-edit-buttons {
display: flex;
gap: 8px;
margin-top: 8px;
justify-content: flex-end;
}
.mobile-edit-confirm,
.mobile-edit-cancel {
.kga-mobile-edit-confirm,
.kga-mobile-edit-cancel {
width: 40px;
height: 40px;
border: none;
@ -45,12 +45,12 @@
justify-content: center;
}
.mobile-edit-confirm {
.kga-mobile-edit-confirm {
background: var(--interactive-accent);
color: var(--text-on-accent);
}
.mobile-edit-cancel {
.kga-mobile-edit-cancel {
background: var(--background-modifier-border);
color: var(--text-normal);
}

View file

@ -1,7 +1,7 @@
/* styles/modals.css */
.error-notice-overlay,
.success-notice-overlay {
.kga-error-notice-overlay,
.kga-success-notice-overlay {
position: fixed;
top: 50%;
left: 50%;
@ -17,15 +17,15 @@
border: 1px solid var(--background-modifier-border);
}
.error-notice-overlay {
.kga-error-notice-overlay {
background: var(--background-modifier-error);
}
.success-notice-overlay {
.kga-success-notice-overlay {
background: var(--background-modifier-success);
}
.token-warning-modal {
.kga-token-warning-modal {
position: fixed;
top: 0;
left: 0;
@ -39,7 +39,7 @@
font-family: var(--font-interface);
}
.token-warning-content {
.kga-token-warning-content {
background: var(--background-primary);
border-radius: 12px;
padding: 20px;
@ -52,7 +52,7 @@
border: 1px solid var(--background-modifier-border);
}
.token-warning-header {
.kga-token-warning-header {
display: flex;
flex-direction: column;
align-items: center;
@ -61,7 +61,7 @@
gap: 8px;
}
.token-warning-header-icon {
.kga-token-warning-header-icon {
width: 40px;
height: 40px;
border-radius: 50%;
@ -74,7 +74,7 @@
flex-shrink: 0;
}
.token-warning-title {
.kga-token-warning-title {
margin: 0;
color: var(--text-normal);
font-size: 18px;
@ -82,14 +82,14 @@
line-height: 1.2;
}
.token-warning-description {
.kga-token-warning-description {
margin: 0;
color: var(--text-muted);
font-size: 14px;
line-height: 1.4;
}
.token-warning-details {
.kga-token-warning-details {
background: var(--background-secondary);
border-radius: 10px;
padding: 20px;
@ -97,67 +97,67 @@
border: 1px solid var(--background-modifier-border);
}
.token-warning-stats {
.kga-token-warning-stats {
display: flex;
gap: 16px;
justify-content: space-between;
margin-bottom: 12px;
}
.token-stat-item {
.kga-token-stat-item {
text-align: center;
flex: 1;
}
.token-stat-number {
.kga-token-stat-number {
font-size: 20px;
font-weight: 700;
line-height: 1;
color: var(--text-accent);
}
.token-stat-number.orange {
.kga-token-stat-number.kga-orange {
color: var(--color-orange);
}
.token-stat-label {
.kga-token-stat-label {
font-size: 12px;
color: var(--text-muted);
margin-top: 4px;
}
.token-warning-recommendation {
.kga-token-warning-recommendation {
border-top: 1px solid var(--background-modifier-border);
padding-top: 12px;
margin-top: 12px;
}
.token-warning-recommendation-header {
.kga-token-warning-recommendation-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 12px;
}
.token-warning-recommendation-content {
.kga-token-warning-recommendation-content {
flex: 1;
}
.token-warning-recommendation-title {
.kga-token-warning-recommendation-title {
font-size: 14px;
font-weight: 600;
color: var(--text-normal);
margin-bottom: 6px;
}
.token-warning-recommendation-text {
.kga-token-warning-recommendation-text {
font-size: 13px;
color: var(--text-muted);
line-height: 1.4;
text-align: center;
}
.token-warning-over-limit {
.kga-token-warning-over-limit {
background: var(--background-modifier-error);
border: 1px solid var(--text-error);
border-radius: 6px;
@ -168,34 +168,34 @@
gap: 12px;
}
.token-warning-over-limit-icon {
.kga-token-warning-over-limit-icon {
font-size: 20px;
flex-shrink: 0;
}
.token-warning-over-limit-text {
.kga-token-warning-over-limit-text {
flex: 1;
}
.token-warning-over-limit-title {
.kga-token-warning-over-limit-title {
font-weight: bold;
color: var(--text-error);
margin-bottom: 4px;
}
.token-warning-over-limit-description {
.kga-token-warning-over-limit-description {
font-size: 12px;
color: var(--text-muted);
}
.token-warning-actions {
.kga-token-warning-actions {
display: flex;
gap: 8px;
justify-content: flex-end;
padding-top: 8px;
}
.token-warning-btn {
.kga-token-warning-btn {
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
@ -205,17 +205,17 @@
border: none;
}
.token-warning-btn-cancel {
.kga-token-warning-btn-cancel {
background: var(--interactive-normal);
color: var(--text-normal);
}
.token-warning-btn-settings,
.token-warning-btn-proceed {
.kga-token-warning-btn-settings,
.kga-token-warning-btn-proceed {
background: var(--interactive-accent);
color: var(--text-on-accent);
}
.token-warning-btn-settings {
.kga-token-warning-btn-settings {
border: 1px solid var(--interactive-accent);
}

View file

@ -1,6 +1,6 @@
/* styles/preview.css */
#correctionPopup .preview-section {
#correctionPopup .kga-preview-section {
background: var(--background-secondary);
border-radius: 8px;
padding: 16px;
@ -12,7 +12,7 @@
overflow: hidden;
}
#correctionPopup .preview-label {
#correctionPopup .kga-preview-label {
font-size: 12px;
font-weight: 600;
color: var(--text-muted);
@ -21,7 +21,7 @@
margin-bottom: 8px;
}
#correctionPopup .preview-hint {
#correctionPopup .kga-preview-hint {
font-size: 11px;
font-weight: normal;
color: var(--text-muted);
@ -30,7 +30,7 @@
opacity: 0.8;
}
#correctionPopup .color-legend {
#correctionPopup .kga-color-legend {
display: flex;
align-items: center;
gap: 10px;
@ -44,7 +44,7 @@
flex-shrink: 0;
}
#correctionPopup .color-legend-item {
#correctionPopup .kga-color-legend-item {
display: flex;
align-items: center;
gap: 4px;
@ -52,7 +52,7 @@
font-weight: 500;
}
#correctionPopup .color-legend-dot {
#correctionPopup .kga-color-legend-dot {
width: 10px;
height: 10px;
border-radius: 50%;
@ -60,32 +60,32 @@
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
#correctionPopup .color-legend-dot.error {
#correctionPopup .kga-color-legend-dot.error {
background: var(--color-red);
border: 1px solid rgba(var(--color-red-rgb), 0.3);
}
#correctionPopup .color-legend-dot.corrected {
#correctionPopup .kga-color-legend-dot.corrected {
background: var(--color-green);
border: 1px solid rgba(var(--color-green-rgb), 0.3);
}
#correctionPopup .color-legend-dot.exception-processed {
#correctionPopup .kga-color-legend-dot.exception-processed {
background: var(--color-blue);
border: 1px solid rgba(var(--color-blue-rgb), 0.3);
}
#correctionPopup .color-legend-dot.original-kept {
#correctionPopup .kga-color-legend-dot.original-kept {
background: #e67e00; /* 진한 주황색 */
border: 1px solid rgba(255, 165, 0, 0.5);
}
#correctionPopup .color-legend-dot.user-edited {
#correctionPopup .kga-color-legend-dot.user-edited {
background: #8b5cf6; /* 보라색 */
border: 1px solid rgba(139, 92, 246, 0.3);
}
#correctionPopup .preview-header {
#correctionPopup .kga-preview-header {
display: flex;
justify-content: space-between;
align-items: center;
@ -95,14 +95,14 @@
min-height: 36px; /* 일관된 높이 보장 */
}
#correctionPopup .pagination-controls {
#correctionPopup .kga-pagination-controls {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
}
#correctionPopup .pagination-btn {
#correctionPopup .kga-pagination-btn {
padding: 6px 12px;
font-size: 13px;
border: 1px solid var(--background-modifier-border);
@ -117,23 +117,23 @@
justify-content: center;
}
#correctionPopup .pagination-btn.small {
#correctionPopup .kga-pagination-btn.small {
padding: 4px 8px;
font-size: 12px;
min-height: 28px;
}
#correctionPopup .pagination-btn:hover:not(:disabled) {
#correctionPopup .kga-pagination-btn:hover:not(:disabled) {
background: var(--interactive-hover);
border-color: var(--interactive-accent);
}
#correctionPopup .pagination-btn:disabled {
#correctionPopup .kga-pagination-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
#correctionPopup .page-info {
#correctionPopup .kga-page-info {
font-size: 13px;
color: var(--text-normal);
min-width: 60px;
@ -145,13 +145,13 @@
min-height: 32px;
}
#correctionPopup .page-info.small {
#correctionPopup .kga-page-info.small {
font-size: 12px;
min-width: 50px;
min-height: 28px;
}
#correctionPopup .page-chars-info {
#correctionPopup .kga-page-chars-info {
font-size: 12px;
color: var(--text-muted);
margin-left: 8px;
@ -167,7 +167,7 @@
justify-content: center;
}
#correctionPopup .preview-text {
#correctionPopup .kga-preview-text {
font-size: 15px;
line-height: 1.7;
color: var(--text-normal);

View file

@ -17,7 +17,7 @@
box-sizing: border-box;
}
#correctionPopup .popup-content {
#correctionPopup .kga-popup-content {
/* 화면 전체를 가득 채움 */
width: 100vw;
height: 100vh;
@ -36,46 +36,46 @@
flex-direction: column;
}
#correctionPopup .header {
#correctionPopup .kga-header {
/* 상단 safe area + 최소 패딩만 적용 */
padding: 8px 16px 8px 16px;
padding-top: max(8px, env(safe-area-inset-top, 0px) + 8px);
padding-left: max(16px, env(safe-area-inset-left, 0px) + 16px);
padding-right: max(16px, env(safe-area-inset-right, 0px) + 16px);
/* 헤더 고정 높이로 공간 절약 */
flex-shrink: 0;
}
#correctionPopup .header h2 {
#correctionPopup .kga-header h2 {
font-size: 16px;
}
#correctionPopup .content {
#correctionPopup .kga-content {
/* 콘텐츠 영역 확대 - 남은 공간 모두 사용 */
flex: 1;
padding: 8px 16px 0;
padding-left: max(16px, env(safe-area-inset-left, 0px) + 16px);
padding-right: max(16px, env(safe-area-inset-right, 0px) + 16px);
/* 스크롤 가능하도록 설정 */
overflow-y: auto;
min-height: 0; /* flex 항목이 축소될 수 있도록 */
}
#correctionPopup .preview-section {
#correctionPopup .kga-preview-section {
/* 미리보기 영역 컴팩트화 */
padding: 8px;
margin-bottom: 8px;
}
#correctionPopup .preview-header {
#correctionPopup .kga-preview-header {
flex-direction: column;
align-items: flex-start;
gap: 4px; /* 8px에서 4px로 줄임 */
}
#correctionPopup .color-legend {
#correctionPopup .kga-color-legend {
order: 1;
align-self: flex-start;
margin-left: 0;
@ -87,55 +87,55 @@
border-radius: 4px;
}
#correctionPopup .preview-label {
#correctionPopup .kga-preview-label {
order: 0;
}
#correctionPopup .color-legend-dot {
#correctionPopup .kga-color-legend-dot {
width: 8px;
height: 8px;
}
#correctionPopup .pagination-controls {
#correctionPopup .kga-pagination-controls {
order: 2;
align-self: flex-end;
}
#correctionPopup .pagination-btn {
#correctionPopup .kga-pagination-btn {
padding: 8px 12px;
font-size: 14px;
min-height: 36px;
min-width: 60px;
}
#correctionPopup .pagination-btn.small {
#correctionPopup .kga-pagination-btn.small {
padding: 6px 10px;
font-size: 12px;
min-height: 32px;
min-width: 50px;
}
#correctionPopup .page-info {
#correctionPopup .kga-page-info {
font-size: 14px;
min-width: 60px;
}
#correctionPopup .page-info.small {
#correctionPopup .kga-page-info.small {
font-size: 12px;
min-width: 50px;
}
#correctionPopup .error-item {
#correctionPopup .kga-error-item {
padding: 10px;
margin-bottom: 10px;
}
#correctionPopup .error-original {
#correctionPopup .kga-error-original {
font-size: 14px;
padding: 6px 10px;
}
#correctionPopup .suggestion-option {
#correctionPopup .kga-suggestion-option {
padding: 8px 12px;
font-size: 13px;
min-height: 36px;
@ -144,7 +144,7 @@
border-radius: 16px;
}
#correctionPopup .button-area {
#correctionPopup .kga-button-area {
/* 버튼 영역 최소화 - 하단 safe area + 최소 패딩만 */
flex-shrink: 0;
gap: 10px;
@ -154,8 +154,8 @@
padding-bottom: max(8px, env(safe-area-inset-bottom, 0px) + 8px);
}
#correctionPopup .cancel-btn,
#correctionPopup .apply-btn {
#correctionPopup .kga-cancel-btn,
#correctionPopup .kga-apply-btn {
padding: 12px 20px;
font-size: 15px;
min-height: 44px;
@ -163,7 +163,7 @@
flex: 1;
}
.clickable-error {
.kga-clickable-error {
padding: 4px 2px;
margin: 0 1px;
border-radius: 3px;
@ -172,37 +172,37 @@
align-items: center;
}
#correctionPopup .preview-text {
#correctionPopup .kga-preview-text {
font-size: 14px;
line-height: 1.6;
min-height: 120px; /* 미리보기 최소 높이 줄임 */
-webkit-overflow-scrolling: touch;
}
#correctionPopup .error-summary:not(.collapsed) .error-summary-content {
#correctionPopup .kga-error-summary:not(.collapsed) .kga-error-summary-content {
/* 전체 화면에 맞게 오류 상세 영역 확대 */
height: 320px;
max-height: 40vh; /* 뷰포트 높이의 40%까지 사용 */
}
#correctionPopup .error-suggestions {
#correctionPopup .kga-error-suggestions {
gap: 8px;
}
#correctionPopup .error-summary {
#correctionPopup .kga-error-summary {
/* 상단 여백 줄임 */
margin-top: 8px;
padding-top: 8px;
}
#correctionPopup .close-btn-header {
#correctionPopup .kga-close-btn-header {
width: 36px;
height: 36px;
font-size: 20px;
}
/* AI 분석 버튼 모바일 최적화 */
#correctionPopup .ai-analyze-btn {
#correctionPopup .kga-ai-analyze-btn {
padding: 4px 8px !important;
font-size: 11px !important;
border-radius: 4px !important;
@ -210,50 +210,50 @@
@media (max-height: 700px) {
/* 작은 화면에서 오류 상세 영역 높이 조정 */
#correctionPopup .error-summary:not(.collapsed) .error-summary-content {
#correctionPopup .kga-error-summary:not(.collapsed) .kga-error-summary-content {
height: 250px;
max-height: 35vh;
}
#correctionPopup .preview-text {
#correctionPopup .kga-preview-text {
min-height: 100px;
}
}
@media (max-width: 896px) and (orientation: landscape) {
/* 가로 모드에서 더 컴팩트한 레이아웃 */
#correctionPopup .header {
#correctionPopup .kga-header {
padding: 6px 16px 6px 16px;
padding-top: max(6px, env(safe-area-inset-top, 0px) + 6px);
}
#correctionPopup .content {
#correctionPopup .kga-content {
padding: 6px 16px 0;
}
#correctionPopup .button-area {
#correctionPopup .kga-button-area {
padding: 6px 16px 6px 16px;
padding-bottom: max(6px, env(safe-area-inset-bottom, 0px) + 6px);
}
#correctionPopup .error-summary:not(.collapsed) .error-summary-content {
#correctionPopup .kga-error-summary:not(.collapsed) .kga-error-summary-content {
height: 200px;
max-height: 30vh;
}
}
#correctionPopup .error-item-compact {
#correctionPopup .kga-error-item-compact {
padding: 10px;
margin-bottom: 8px;
}
#correctionPopup .error-original-compact {
#correctionPopup .kga-error-original-compact {
font-size: 12px;
padding: 3px 6px;
min-width: 50px;
}
#correctionPopup .suggestion-compact {
#correctionPopup .kga-suggestion-compact {
font-size: 12px;
padding: 4px 8px;
min-height: 32px;
@ -262,7 +262,7 @@
align-items: center;
}
#correctionPopup .error-help-compact {
#correctionPopup .kga-error-help-compact {
font-size: 11px !important;
padding: 5px 9px !important;
min-height: 22px !important;

View file

@ -1,15 +1,15 @@
/* styles/settings.css */
.ai-disabled-desc {
.kga-ai-disabled-desc {
color: var(--text-muted);
margin-bottom: 20px;
}
.ignored-words-empty {
.kga-ignored-words-empty {
text-align: center;
}
.ignored-word-tag {
.kga-ignored-word-tag {
display: inline-block;
background: var(--interactive-accent);
color: white;
@ -22,11 +22,11 @@
transition: background 0.2s ease;
}
.ignored-word-tag:hover {
.kga-ignored-word-tag:hover {
background: var(--interactive-accent-hover);
}
.remove-word-btn {
.kga-remove-word-btn {
margin-left: 6px;
font-weight: bold;
cursor: pointer;
@ -35,16 +35,16 @@
opacity: 0.7;
}
.remove-word-btn:hover {
.kga-remove-word-btn:hover {
opacity: 1;
}
.settings-error {
.kga-settings-error {
color: var(--text-error);
margin-top: 10px;
}
.tag-cloud-section {
.kga-tag-cloud-section {
border: 1px solid var(--background-modifier-border);
border-radius: 8px;
background: var(--background-secondary);
@ -52,7 +52,7 @@
margin-bottom: 20px;
}
.tag-cloud-header {
.kga-tag-cloud-header {
display: flex;
justify-content: space-between;
align-items: center;
@ -61,7 +61,7 @@
background: var(--background-primary);
}
.tag-cloud-label {
.kga-tag-cloud-label {
font-size: 12px;
font-weight: 600;
color: var(--text-muted);
@ -69,13 +69,13 @@
letter-spacing: 0.5px;
}
.clear-all-button {
.kga-clear-all-button {
padding: 4px 8px;
font-size: 11px;
border-radius: 4px;
}
.ignored-words-container {
.kga-ignored-words-container {
padding: 12px;
min-height: 80px;
max-height: 180px;
@ -87,20 +87,20 @@
align-content: flex-start;
}
.add-word-container {
.kga-add-word-container {
width: 100%;
display: flex;
flex-direction: column;
gap: 12px;
}
.input-row {
.kga-input-row {
display: flex;
gap: 8px;
align-items: flex-start;
}
.add-word-input {
.kga-add-word-input {
flex: 1;
padding: 8px 12px;
border: 1px solid var(--background-modifier-border);
@ -110,19 +110,19 @@
color: var(--text-normal);
}
.add-word-input:focus {
.kga-add-word-input:focus {
outline: none;
border-color: var(--interactive-accent);
}
.add-word-button {
.kga-add-word-button {
padding: 8px 16px;
white-space: nowrap;
font-size: 13px;
cursor: pointer;
}
.tag-preview-container {
.kga-tag-preview-container {
min-height: 40px;
padding: 8px;
background: var(--background-secondary);
@ -135,11 +135,11 @@
align-items: center;
}
.tag-preview-container.visible {
.kga-tag-preview-container.visible {
display: flex;
}
.preview-label {
.kga-preview-label {
font-size: 12px;
color: var(--text-muted);
margin-right: 8px;
@ -147,7 +147,7 @@
font-weight: 500;
}
.preview-tag {
.kga-preview-tag {
display: inline-block;
padding: 3px 8px;
border-radius: 12px;
@ -157,14 +157,14 @@
transition: background 0.2s ease;
}
.preview-tag.already-ignored {
.kga-preview-tag.already-ignored {
background: var(--background-modifier-border);
color: var(--text-muted);
text-decoration: line-through;
opacity: 0.8;
}
.preview-tag.new-tag {
.kga-preview-tag.new-tag {
background: var(--interactive-accent);
color: var(--text-on-accent);
cursor: pointer;

View file

@ -1,7 +1,7 @@
/* styles/tooltip.css */
/* === Tooltip Container === */
.popup-tooltip-container {
.kga-popup-tooltip-container {
position: fixed;
top: 0;
left: 0;
@ -10,7 +10,7 @@
}
/* === Base Tooltip Styles === */
.popup-tooltip {
.kga-popup-tooltip {
position: absolute;
background: var(--background-primary);
border: 1px solid var(--background-modifier-border);
@ -25,131 +25,131 @@
}
/* Default max-width for all tooltips */
.popup-tooltip:not(.help-tooltip):not(.button-hint-tooltip):not(.navigation-hint-tooltip) {
.kga-popup-tooltip:not(.kga-help-tooltip):not(.kga-button-hint-tooltip):not(.kga-navigation-hint-tooltip) {
max-width: 300px;
}
/* === Tooltip Animation States === */
.popup-tooltip.kga-tooltip-enter {
.kga-popup-tooltip.kga-tooltip-enter {
opacity: 0;
transform: translateY(-5px);
}
.popup-tooltip.kga-tooltip-visible {
.kga-popup-tooltip.kga-tooltip-visible {
opacity: 1;
transform: translateY(0);
}
.popup-tooltip.kga-tooltip-exit {
.kga-popup-tooltip.kga-tooltip-exit {
opacity: 0;
transform: translateY(-5px);
}
/* === Dynamic Positioning === */
.popup-tooltip.kga-dynamic-position {
.kga-popup-tooltip.kga-dynamic-position {
left: var(--kga-pos-left);
top: var(--kga-pos-top);
}
/* === Tooltip Type Classes === */
.error-preview-tooltip {
.kga-error-preview-tooltip {
border-color: var(--text-error);
border-left: 3px solid var(--text-error);
}
.suggestion-tooltip {
.kga-suggestion-tooltip {
border-color: var(--text-accent);
border-left: 3px solid var(--text-accent);
}
.ai-info-tooltip {
.kga-ai-info-tooltip {
border-color: var(--interactive-accent);
border-left: 3px solid var(--interactive-accent);
background: var(--background-secondary);
}
.help-tooltip {
.kga-help-tooltip {
border-color: var(--text-muted);
max-width: 250px;
}
.button-hint-tooltip {
.kga-button-hint-tooltip {
font-size: 11px;
padding: 6px 10px;
}
.navigation-hint-tooltip {
.kga-navigation-hint-tooltip {
font-size: 11px;
padding: 6px 10px;
}
/* === Tooltip Content Sections === */
.error-preview-content .error-title {
.kga-error-preview-content .kga-error-title {
font-weight: 600;
margin-bottom: 4px;
color: var(--text-error);
}
.error-preview-content .error-original {
.kga-error-preview-content .kga-error-original {
margin-bottom: 4px;
font-style: italic;
}
.error-preview-content .error-hint {
.kga-error-preview-content .kga-error-hint {
font-size: 10px;
color: var(--text-muted);
margin-top: 4px;
}
.suggestion-tooltip-content .suggestion-text {
.kga-suggestion-tooltip-content .kga-suggestion-text {
font-weight: 500;
margin-bottom: 4px;
}
.suggestion-tooltip-content .suggestion-hint {
.kga-suggestion-tooltip-content .kga-suggestion-hint {
font-size: 10px;
color: var(--text-muted);
}
.ai-info-content .ai-title {
.kga-ai-info-content .kga-ai-title {
font-weight: 600;
margin-bottom: 6px;
}
.ai-info-content .ai-confidence {
.kga-ai-info-content .kga-ai-confidence {
font-size: 11px;
margin-bottom: 4px;
color: var(--interactive-accent);
}
.ai-info-content .ai-reasoning {
.kga-ai-info-content .kga-ai-reasoning {
font-size: 11px;
color: var(--text-muted);
line-height: 1.5;
}
.help-tooltip-content .help-text {
.kga-help-tooltip-content .kga-help-text {
line-height: 1.5;
}
.button-hint-content .button-name {
.kga-button-hint-content .kga-button-name {
font-weight: 500;
margin-bottom: 2px;
}
.button-hint-content .button-shortcut {
.kga-button-hint-content .kga-button-shortcut {
font-size: 10px;
color: var(--text-muted);
font-family: var(--font-monospace);
}
.navigation-hint-content .navigation-text {
.kga-navigation-hint-content .kga-navigation-text {
font-weight: 500;
}
/* === Responsive Adjustments === */
@media (max-width: 768px) {
.popup-tooltip {
.kga-popup-tooltip {
max-width: 250px;
font-size: 11px;
}

View file

@ -1,7 +1,7 @@
/* styles/virtual-scroller.css */
/* Virtual Scroller component styles using CSS variables */
.virtual-scroller-container {
.kga-virtual-scroller-container {
width: 100%;
height: 100%;
display: flex;
@ -12,7 +12,7 @@
padding-bottom: var(--scroll-padding-bottom, 0);
}
.virtual-scroller-viewport {
.kga-virtual-scroller-viewport {
height: var(--vs-viewport-height, 400px);
overflow-y: auto;
overflow-x: hidden;
@ -22,32 +22,32 @@
will-change: scroll-position;
}
.virtual-scroller-viewport::-webkit-scrollbar {
.kga-virtual-scroller-viewport::-webkit-scrollbar {
width: 6px;
}
.virtual-scroller-viewport::-webkit-scrollbar-track {
.kga-virtual-scroller-viewport::-webkit-scrollbar-track {
background: transparent;
}
.virtual-scroller-viewport::-webkit-scrollbar-thumb {
.kga-virtual-scroller-viewport::-webkit-scrollbar-thumb {
background: var(--background-modifier-border);
border-radius: 3px;
min-height: 40px;
}
.virtual-scroller-viewport::-webkit-scrollbar-thumb:hover {
.kga-virtual-scroller-viewport::-webkit-scrollbar-thumb:hover {
background: var(--background-modifier-border-hover);
}
.virtual-scroller-content {
.kga-virtual-scroller-content {
position: relative;
width: 100%;
height: var(--vs-content-height, 100%);
min-height: 100%;
}
.virtual-scroller-item {
.kga-virtual-scroller-item {
position: absolute;
width: 100%;
height: var(--vs-item-height, 30px);