변경 목적: Agent Compare 아래 비교본의 개발 문서와 런처 소스를 기준으로 현재 AX Commander에 빠져 있던 신규 런처 기능을 동일한 흐름으로 옮겨, 비교본 수준의 기능 폭을 현재 제품에 반영했습니다. 핵심 수정사항: 비교본의 신규 런처 핸들러 다수를 src/AxCopilot/Handlers로 이식하고 App.xaml.cs 등록 흐름에 연결했습니다. 빠른 링크, 파일 태그, 알림 센터, 포모도로, 파일 브라우저, 핫키 관리, OCR, 세션/스케줄/매크로, Git/정규식/네트워크/압축/해시/UUID/JWT/QR 등 AX Commander 기능을 추가했습니다. 핵심 수정사항: 신규 기능이 실제 동작하도록 AppSettings 확장, SchedulerService/FileTagService/NotificationCenterService/IconCacheService/UrlTemplateEngine/PomodoroService 추가, 배치 이름변경/세션/스케줄/매크로 편집 창 추가, NotificationService와 Symbols 보강, QR/OCR용 csproj 의존성과 Windows 타겟 프레임워크를 반영했습니다. 문서 반영: README.md와 docs/DEVELOPMENT.md에 비교본 기반 런처 기능 이식 이력과 검증 결과를 업데이트했습니다. 검증 결과: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\ 실행 기준 경고 0개, 오류 0개를 확인했습니다.
409 lines
23 KiB
C#
409 lines
23 KiB
C#
using System.IO;
|
|
using System.Text.Encodings.Web;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
using System.Windows;
|
|
using AxCopilot.SDK;
|
|
using AxCopilot.Services;
|
|
|
|
namespace AxCopilot.Handlers;
|
|
|
|
/// <summary>
|
|
/// L24-3: 자주 틀리는 한국어 맞춤법 핸들러. "spell" 프리픽스로 사용합니다.
|
|
///
|
|
/// 예: spell → 클립보드 텍스트 맞춤법 검사
|
|
/// spell 되 → "되/돼" 관련 오류 목록
|
|
/// spell <단어> → 해당 단어 맞춤법 확인
|
|
/// spell list → 전체 오류 목록 카테고리
|
|
/// Enter → 올바른 표현 클립보드 복사
|
|
/// </summary>
|
|
public class SpellHandler : IActionHandler
|
|
{
|
|
public string? Prefix => "spell";
|
|
|
|
public PluginMetadata Metadata => new(
|
|
"맞춤법",
|
|
"자주 틀리는 한국어 맞춤법 참조 — 되/돼·안/않·혼동어·외래어 등",
|
|
"1.0",
|
|
"AX");
|
|
|
|
private sealed record SpellEntry(string Wrong, string Correct, string Explanation, string Category);
|
|
|
|
private static readonly SpellEntry[] Entries =
|
|
[
|
|
// ── 되/돼 ────────────────────────────────────────────────────────────
|
|
new("됬다", "됐다", "됐다(되었다의 준말)", "되/돼"),
|
|
new("됬어", "됐어", "됐어(되었어의 준말)", "되/돼"),
|
|
new("됬나요", "됐나요", "됐나요(되었나요의 준말)", "되/돼"),
|
|
new("됬습니다", "됐습니다", "됐습니다(되었습니다의 준말)", "되/돼"),
|
|
new("돼었다", "됐다", "됐다(되었다의 준말)", "되/돼"),
|
|
new("안됬어", "안 됐어", "됐다(되었다의 준말), 안은 띄어씀", "되/돼"),
|
|
new("어떻게됬나요", "어떻게 됐나요","됐나요(되었나요의 준말)", "되/돼"),
|
|
new("잘됬다", "잘됐다", "잘됐다(잘 되었다의 준말)", "되/돼"),
|
|
new("해됬다", "해 됐다", "됐다(되었다의 준말)", "되/돼"),
|
|
new("이렇게됬다", "이렇게 됐다", "됐다(되었다의 준말), 띄어씀 주의", "되/돼"),
|
|
|
|
// ── 안/않 ────────────────────────────────────────────────────────────
|
|
new("안되다", "안 되다", "'안'은 부사이므로 띄어씀", "안/않"),
|
|
new("안하다", "안 하다", "'안'은 부사이므로 띄어씀", "안/않"),
|
|
new("않되다", "안 되다", "'않다'는 '아니하다'의 준말로 용언", "안/않"),
|
|
new("하지않다", "하지 않다", "'않다'는 용언, 앞 단어와 띄어씀", "안/않"),
|
|
new("하지않고", "하지 않고", "'않고'는 용언, 앞 단어와 띄어씀", "안/않"),
|
|
new("하지않아", "하지 않아", "'않아'는 용언, 앞 단어와 띄어씀", "안/않"),
|
|
new("되지않다", "되지 않다", "'않다'는 용언, 앞 단어와 띄어씀", "안/않"),
|
|
new("않됩니다", "안 됩니다", "'않다'는 '아니하다', '안'은 부사", "안/않"),
|
|
|
|
// ── 혼동어 ──────────────────────────────────────────────────────────
|
|
new("로서/로써", "로서(자격), 로써(수단)", "의미 구분: 학생으로서(자격), 말로써(수단)", "혼동어"),
|
|
new("낫다/낳다", "낫다(회복), 낳다(출산)", "의미 구분: 병이 낫다 / 아이를 낳다", "혼동어"),
|
|
new("맞히다/맞추다","맞히다(정답), 맞추다(조합)","의미 구분: 문제를 맞히다 / 퍼즐을 맞추다", "혼동어"),
|
|
new("반드시/반듯이","반드시(꼭), 반듯이(곧게)", "의미 구분: 반드시 와라 / 반듯이 서라", "혼동어"),
|
|
new("부치다/붙이다","부치다(편지), 붙이다(접착)","의미 구분: 편지를 부치다 / 우표를 붙이다", "혼동어"),
|
|
new("이따가/있다가","이따가(나중에), 있다가(머물다가)","의미 구분: 이따가 와라 / 집에 있다가 나와", "혼동어"),
|
|
new("웬/왠", "웬(어떤), 왠지(왜인지)", "의미 구분: 웬 일이니 / 왠지 모르게", "혼동어"),
|
|
new("어떻게/어떡해","어떻게(방법), 어떡해(어떻게 해)","의미 구분: 어떻게 해야 하나 / 이걸 어떡해", "혼동어"),
|
|
new("바라다/바래다","바라다(희망), 바래다(색이 변함)","의미 구분: 합격을 바란다 / 색이 바랬다", "혼동어"),
|
|
new("~던지/~든지", "~던지(과거경험), ~든지(선택)","의미 구분: 얼마나 좋았던지 / 뭐든지 해라", "혼동어"),
|
|
new("틀리다/다르다","틀리다(오답), 다르다(차이)", "의미 구분: 답이 틀렸다 / 나와 다르다", "혼동어"),
|
|
|
|
// ── 맞춤법 ──────────────────────────────────────────────────────────
|
|
new("설레임", "설렘", "표준어는 '설렘'", "맞춤법"),
|
|
new("오랫만에", "오랜만에", "표준어는 '오랜만에'", "맞춤법"),
|
|
new("왠만하면", "웬만하면", "표준어는 '웬만하면'", "맞춤법"),
|
|
new("몇일", "며칠", "표준어는 '며칠'", "맞춤법"),
|
|
new("어의없다", "어이없다", "표준어는 '어이없다'", "맞춤법"),
|
|
new("내노라하는", "내로라하는", "표준어는 '내로라하는'", "맞춤법"),
|
|
new("금새", "금세", "표준어는 '금세' ('금시에'의 준말)", "맞춤법"),
|
|
new("새벽녁", "새벽녘", "표준어는 '새벽녘'", "맞춤법"),
|
|
new("무릎쓰고", "무릅쓰고", "표준어는 '무릅쓰고'", "맞춤법"),
|
|
new("짜집기", "짜깁기", "표준어는 '짜깁기'", "맞춤법"),
|
|
new("역활", "역할", "표준어는 '역할'", "맞춤법"),
|
|
new("희안하다", "희한하다", "표준어는 '희한하다'", "맞춤법"),
|
|
new("요컨데", "요컨대", "표준어는 '요컨대'", "맞춤법"),
|
|
new("알맞는", "알맞은", "형용사이므로 '알맞은'이 맞음", "맞춤법"),
|
|
new("예쁘다/이쁘다","예쁘다가 표준어","'이쁘다'는 비표준어", "맞춤법"),
|
|
new("이따위", "이따위", "표준어 (맞음)", "맞춤법"),
|
|
new("구렛나루", "구레나룻", "표준어는 '구레나룻'", "맞춤법"),
|
|
new("꼭두각시", "꼭두각시", "표준어 (맞음)", "맞춤법"),
|
|
|
|
// ── 띄어쓰기 ────────────────────────────────────────────────────────
|
|
new("할수있다", "할 수 있다", "의존명사 '수'는 띄어씀", "띄어쓰기"),
|
|
new("해야한다", "해야 한다", "'한다'는 독립 서술어, 띄어씀", "띄어쓰기"),
|
|
new("것같다", "것 같다", "의존명사 '것'은 띄어씀", "띄어쓰기"),
|
|
new("수밖에없다", "수밖에 없다", "'없다'는 독립 서술어, 띄어씀", "띄어쓰기"),
|
|
new("한번", "한 번(횟수), 한번(시도)","횟수=한 번 / 시도=한번", "띄어쓰기"),
|
|
new("이상한거같다", "이상한 것 같다","'것'은 의존명사로 띄어씀", "띄어쓰기"),
|
|
new("될것같다", "될 것 같다", "'것'은 의존명사로 띄어씀", "띄어쓰기"),
|
|
new("할것이다", "할 것이다", "'것'은 의존명사로 띄어씀", "띄어쓰기"),
|
|
new("있을때", "있을 때", "'때'는 의존명사로 띄어씀", "띄어쓰기"),
|
|
new("모를때", "모를 때", "'때'는 의존명사로 띄어씀", "띄어쓰기"),
|
|
|
|
// ── 외래어 ──────────────────────────────────────────────────────────
|
|
new("리더쉽", "리더십", "표준 외래어: ~ship은 '십'으로", "외래어"),
|
|
new("멤버쉽", "멤버십", "표준 외래어: ~ship은 '십'으로", "외래어"),
|
|
new("파트너쉽", "파트너십", "표준 외래어: ~ship은 '십'으로", "외래어"),
|
|
new("인턴쉽", "인턴십", "표준 외래어: ~ship은 '십'으로", "외래어"),
|
|
new("메세지", "메시지", "표준 외래어: message → 메시지", "외래어"),
|
|
new("써비스", "서비스", "표준 외래어: service → 서비스", "외래어"),
|
|
new("케릭터", "캐릭터", "표준 외래어: character → 캐릭터", "외래어"),
|
|
new("컨텐츠", "콘텐츠", "표준 외래어: contents → 콘텐츠", "외래어"),
|
|
new("엑기스", "엑스", "표준 외래어: extract → 엑기스 (비표준)","외래어"),
|
|
new("렌트카", "렌터카", "표준 외래어: rent-a-car → 렌터카", "외래어"),
|
|
new("쥬스", "주스", "표준 외래어: juice → 주스", "외래어"),
|
|
new("후라이", "프라이", "표준 외래어: fry → 프라이", "외래어"),
|
|
new("후라이팬", "프라이팬", "표준 외래어: frying pan → 프라이팬", "외래어"),
|
|
new("비스켓", "비스킷", "표준 외래어: biscuit → 비스킷", "외래어"),
|
|
new("떼레비", "텔레비전", "표준 외래어: television → 텔레비전", "외래어"),
|
|
];
|
|
|
|
private static readonly string[] CategoryOrder = ["되/돼", "안/않", "혼동어", "맞춤법", "띄어쓰기", "외래어", "사용자"];
|
|
|
|
// ─── 사용자 정의 항목 (L29-3) ────────────────────────────────────────────
|
|
private sealed record CustomSpell(
|
|
[property: JsonPropertyName("wrong")] string Wrong,
|
|
[property: JsonPropertyName("correct")] string Correct,
|
|
[property: JsonPropertyName("desc")] string Desc);
|
|
|
|
private static readonly string CustomPath = Path.Combine(
|
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
|
"AxCopilot", "spell_custom.json");
|
|
|
|
private static readonly JsonSerializerOptions JsonOpt = new()
|
|
{
|
|
WriteIndented = true,
|
|
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
|
};
|
|
|
|
private static List<CustomSpell> LoadCustom()
|
|
{
|
|
try
|
|
{
|
|
if (!File.Exists(CustomPath)) return [];
|
|
return JsonSerializer.Deserialize<List<CustomSpell>>(File.ReadAllText(CustomPath)) ?? [];
|
|
}
|
|
catch { return []; }
|
|
}
|
|
|
|
private static void SaveCustom(List<CustomSpell> list)
|
|
{
|
|
try
|
|
{
|
|
var dir = Path.GetDirectoryName(CustomPath)!;
|
|
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
|
|
File.WriteAllText(CustomPath, JsonSerializer.Serialize(list, JsonOpt));
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
private IEnumerable<SpellEntry> AllEntries()
|
|
{
|
|
foreach (var e in Entries) yield return e;
|
|
foreach (var c in LoadCustom())
|
|
yield return new SpellEntry(c.Wrong, c.Correct, c.Desc, "사용자");
|
|
}
|
|
|
|
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
|
{
|
|
var q = query.Trim();
|
|
var items = new List<LauncherItem>();
|
|
|
|
// ── add 명령 (L29-3) ─────────────────────────────────────────────────
|
|
if (q.StartsWith("add ", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var parts = q[4..].Trim().Split(' ', 3, StringSplitOptions.RemoveEmptyEntries);
|
|
if (parts.Length < 2)
|
|
{
|
|
items.Add(new LauncherItem("사용법: spell add {틀린표현} {올바른표현} [설명]",
|
|
"예: spell add 어의없다 어이없다 어이(기가 막혀)가 올바른 표현",
|
|
null, null, Symbol: "\uE710"));
|
|
}
|
|
else
|
|
{
|
|
var wrong = parts[0];
|
|
var correct = parts[1];
|
|
var desc = parts.Length >= 3 ? parts[2] : "사용자 추가 항목";
|
|
items.Add(new LauncherItem(
|
|
$"맞춤법 추가: ❌ {wrong} → ✅ {correct}",
|
|
desc,
|
|
null, ("add", $"{wrong}\t{correct}\t{desc}"), Symbol: "\uE710"));
|
|
}
|
|
return Task.FromResult<IEnumerable<LauncherItem>>(items);
|
|
}
|
|
|
|
// ── del 명령 (L29-3) ─────────────────────────────────────────────────
|
|
if (q.StartsWith("del ", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var wrong = q[4..].Trim();
|
|
var custom = LoadCustom();
|
|
var found = custom.FirstOrDefault(c => c.Wrong.Equals(wrong, StringComparison.OrdinalIgnoreCase));
|
|
if (found != null)
|
|
{
|
|
items.Add(new LauncherItem($"사용자 항목 삭제: {found.Wrong} → {found.Correct}",
|
|
found.Desc, null, ("del", found.Wrong), Symbol: "\uE74D"));
|
|
}
|
|
else
|
|
{
|
|
items.Add(new LauncherItem($"'{wrong}' 사용자 항목을 찾을 수 없습니다",
|
|
"기본 항목은 삭제할 수 없습니다", null, null, Symbol: "\uE783"));
|
|
}
|
|
return Task.FromResult<IEnumerable<LauncherItem>>(items);
|
|
}
|
|
|
|
// ── custom 명령 (사용자 항목만 보기) ──────────────────────────────────
|
|
if (q.Equals("custom", StringComparison.OrdinalIgnoreCase) ||
|
|
q.Equals("사용자", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var custom = LoadCustom();
|
|
if (custom.Count == 0)
|
|
{
|
|
items.Add(new LauncherItem("사용자 항목이 없습니다",
|
|
"spell add {틀린표현} {올바른표현} [설명] 으로 추가하세요",
|
|
null, null, Symbol: "\uE7BA"));
|
|
}
|
|
else
|
|
{
|
|
items.Add(new LauncherItem($"사용자 맞춤법 항목 {custom.Count}개", "",
|
|
null, null, Symbol: "\uE7BA"));
|
|
foreach (var c in custom)
|
|
items.Add(MakeSpellItem(new SpellEntry(c.Wrong, c.Correct, c.Desc, "사용자")));
|
|
}
|
|
return Task.FromResult<IEnumerable<LauncherItem>>(items);
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(q))
|
|
{
|
|
// 클립보드 텍스트 맞춤법 검사
|
|
string clipText = "";
|
|
try
|
|
{
|
|
System.Windows.Application.Current.Dispatcher.Invoke(() =>
|
|
{
|
|
if (Clipboard.ContainsText())
|
|
clipText = Clipboard.GetText();
|
|
});
|
|
}
|
|
catch { }
|
|
|
|
if (!string.IsNullOrWhiteSpace(clipText))
|
|
{
|
|
var found = AllEntries().Where(e =>
|
|
clipText.Contains(e.Wrong, StringComparison.OrdinalIgnoreCase)).ToList();
|
|
|
|
if (found.Count > 0)
|
|
{
|
|
items.Add(new LauncherItem($"클립보드에서 맞춤법 오류 {found.Count}개 발견",
|
|
"Enter: 올바른 표현 복사", null, null, Symbol: "\uE7BA"));
|
|
foreach (var e in found)
|
|
items.Add(MakeSpellItem(e));
|
|
return Task.FromResult<IEnumerable<LauncherItem>>(items);
|
|
}
|
|
else
|
|
{
|
|
items.Add(new LauncherItem("클립보드 텍스트에서 오류를 찾지 못했습니다",
|
|
"카테고리: 되/돼 · 안/않 · 맞춤법 · 띄어쓰기 · 혼동어 · 외래어",
|
|
null, null, Symbol: "\uE7BA"));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
items.Add(new LauncherItem($"한국어 맞춤법 참조 {Entries.Length}개",
|
|
"카테고리: 되/돼 · 안/않 · 맞춤법 · 띄어쓰기 · 혼동어 · 외래어",
|
|
null, null, Symbol: "\uE7BA"));
|
|
}
|
|
|
|
// 카테고리 목록
|
|
foreach (var cat in CategoryOrder)
|
|
{
|
|
var cnt = Entries.Count(e => e.Category == cat);
|
|
items.Add(new LauncherItem($"spell {cat}", $"{cat} ({cnt}개)",
|
|
null, null, Symbol: "\uE7BA"));
|
|
}
|
|
return Task.FromResult<IEnumerable<LauncherItem>>(items);
|
|
}
|
|
|
|
var kw = q.ToLowerInvariant();
|
|
|
|
// "list" → 카테고리별 개수
|
|
if (kw == "list")
|
|
{
|
|
var all = AllEntries().ToList();
|
|
items.Add(new LauncherItem($"맞춤법 오류 목록 총 {all.Count}개", "", null, null, Symbol: "\uE7BA"));
|
|
foreach (var cat in CategoryOrder)
|
|
{
|
|
var cnt = all.Count(e => e.Category == cat);
|
|
if (cnt == 0) continue;
|
|
items.Add(new LauncherItem(cat, $"{cnt}개 · spell {cat}로 목록 보기",
|
|
null, null, Symbol: "\uE7BA"));
|
|
}
|
|
return Task.FromResult<IEnumerable<LauncherItem>>(items);
|
|
}
|
|
|
|
// 카테고리 키워드 매핑
|
|
var catMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
["되"] = "되/돼",
|
|
["돼"] = "됩니다",
|
|
["됐"] = "됩니다",
|
|
["됩"] = "됩니다",
|
|
["되/돼"] = "됩니다",
|
|
["안"] = "안/않",
|
|
["않"] = "안/않",
|
|
["안/않"] = "안/않",
|
|
["혼동"] = "혼동어",
|
|
["혼동어"] = "혼동어",
|
|
["맞춤법"] = "맞춤법",
|
|
["맞춤"] = "맞춤법",
|
|
["띄어"] = "띄어쓰기",
|
|
["띄어쓰기"] = "띄어쓰기",
|
|
["외래어"] = "외래어",
|
|
["외래"] = "외래어",
|
|
};
|
|
|
|
// 카테고리 직접 매핑
|
|
foreach (var cat in CategoryOrder)
|
|
{
|
|
if (cat.Equals(q, StringComparison.OrdinalIgnoreCase) ||
|
|
catMap.TryGetValue(q, out var mappedCat) && mappedCat == cat)
|
|
{
|
|
var catList = Entries.Where(e => e.Category == cat).ToList();
|
|
items.Add(new LauncherItem($"{cat} {catList.Count}개",
|
|
"Enter: 올바른 표현 복사", null, null, Symbol: "\uE7BA"));
|
|
foreach (var e in catList)
|
|
items.Add(MakeSpellItem(e));
|
|
return Task.FromResult<IEnumerable<LauncherItem>>(items);
|
|
}
|
|
}
|
|
|
|
// catMap으로 카테고리 찾기
|
|
if (catMap.TryGetValue(q, out var foundCat) && CategoryOrder.Contains(foundCat))
|
|
{
|
|
var catList = Entries.Where(e => e.Category == foundCat).ToList();
|
|
items.Add(new LauncherItem($"{foundCat} {catList.Count}개",
|
|
"Enter: 올바른 표현 복사", null, null, Symbol: "\uE7BA"));
|
|
foreach (var e in catList)
|
|
items.Add(MakeSpellItem(e));
|
|
return Task.FromResult<IEnumerable<LauncherItem>>(items);
|
|
}
|
|
|
|
// 키워드 검색 (내장 + 사용자 항목)
|
|
var searched = AllEntries().Where(e =>
|
|
e.Wrong.Contains(kw, StringComparison.OrdinalIgnoreCase) ||
|
|
e.Correct.Contains(kw, StringComparison.OrdinalIgnoreCase) ||
|
|
e.Explanation.Contains(kw, StringComparison.OrdinalIgnoreCase)).ToList();
|
|
|
|
if (searched.Count == 0)
|
|
{
|
|
items.Add(new LauncherItem($"'{q}' 검색 결과 없음",
|
|
"카테고리: 되/돼 · 안/않 · 맞춤법 · 띄어쓰기 · 혼동어 · 외래어",
|
|
null, null, Symbol: "\uE783"));
|
|
return Task.FromResult<IEnumerable<LauncherItem>>(items);
|
|
}
|
|
|
|
items.Add(new LauncherItem($"'{q}' 검색 결과 {searched.Count}개",
|
|
"Enter: 올바른 표현 복사", null, null, Symbol: "\uE7BA"));
|
|
foreach (var e in searched)
|
|
items.Add(MakeSpellItem(e));
|
|
|
|
return Task.FromResult<IEnumerable<LauncherItem>>(items);
|
|
}
|
|
|
|
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
|
{
|
|
if (item.Data is ("copy", string text))
|
|
{
|
|
try
|
|
{
|
|
System.Windows.Application.Current.Dispatcher.Invoke(
|
|
() => Clipboard.SetText(text));
|
|
NotificationService.Notify("맞춤법", "올바른 표현을 복사했습니다.");
|
|
}
|
|
catch { }
|
|
}
|
|
else if (item.Data is ("add", string addData))
|
|
{
|
|
var parts = addData.Split('\t');
|
|
if (parts.Length >= 3)
|
|
{
|
|
var custom = LoadCustom();
|
|
custom.RemoveAll(c => c.Wrong.Equals(parts[0], StringComparison.OrdinalIgnoreCase));
|
|
custom.Add(new CustomSpell(parts[0], parts[1], parts[2]));
|
|
SaveCustom(custom);
|
|
NotificationService.Notify("맞춤법", $"'{parts[0]} → {parts[1]}' 항목이 추가되었습니다.");
|
|
}
|
|
}
|
|
else if (item.Data is ("del", string delWrong))
|
|
{
|
|
var custom = LoadCustom();
|
|
custom.RemoveAll(c => c.Wrong.Equals(delWrong, StringComparison.OrdinalIgnoreCase));
|
|
SaveCustom(custom);
|
|
NotificationService.Notify("맞춤법", $"'{delWrong}' 항목이 삭제되었습니다.");
|
|
}
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
private static LauncherItem MakeSpellItem(SpellEntry e) =>
|
|
new($"❌ {e.Wrong} → ✅ {e.Correct}",
|
|
e.Explanation,
|
|
null, ("copy", e.Correct), Symbol: "\uE7BA");
|
|
}
|