|
9 | 9 | <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;700&display=swap" rel="stylesheet"> |
10 | 10 | <style> |
11 | 11 | /* ----------------------------------------------------- */ |
12 | | - /* 樣式定義 (略,與前版相同) */ |
| 12 | + /* 樣式定義 */ |
13 | 13 | /* ----------------------------------------------------- */ |
14 | 14 |
|
15 | 15 | :root { |
|
101 | 101 | background-color: var(--primary-color); |
102 | 102 | color: white; |
103 | 103 | cursor: pointer; |
104 | | - box-shadow: 0 2px 5px rgba |
105 | | -(0, 0, 0, 0.1); |
| 104 | + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); |
106 | 105 | transition: background-color 0.2s; |
107 | 106 | } |
108 | 107 |
|
|
127 | 126 | box-shadow: 0 4px 10px rgba(0, 0, 0, 0.08); |
128 | 127 | margin-bottom: 5px; |
129 | 128 | padding: 8px; |
130 | | - height: 20vh; /* Reduced from 40vh to 20vh */ |
| 129 | + height: 20vh; |
131 | 130 | font-size: 1.5rem; |
132 | 131 | line-height: 1.6; |
133 | 132 | white-space: pre-wrap; |
|
231 | 230 | background-color: #e8eaf6; |
232 | 231 | } |
233 | 232 |
|
234 | | - /* ADDED: Toolbar item styles */ |
| 233 | + /* Toolbar item styles */ |
235 | 234 | #candidate-display span.toolbar-tool { |
236 | 235 | font-size: 1.2rem; |
237 | 236 | padding: 5px 15px; |
|
352 | 351 | transform: scaleX(2); |
353 | 352 | } |
354 | 353 |
|
355 | | - /* ADDED: Overlay styles */ |
| 354 | + /* Overlay styles */ |
356 | 355 | #keyboard-overlay { |
357 | 356 | position: absolute; /* 定位會被 JS 覆蓋 */ |
358 | 357 | left: 0; |
|
365 | 364 |
|
366 | 365 | #keyboard-overlay.emoji button { |
367 | 366 | box-sizing: border-box; |
368 | | - /* 由於 flexbox 佈局,float: left 被移除 */ |
369 | 367 | text-align: center; |
370 | 368 | font-size: 1.8rem; |
371 | | - /* ADDED: 使其看起來像獨立的按鍵 */ |
372 | 369 | background-color: var(--key-bg); /* 使用鍵盤背景色 #f9f9f9 */ |
373 | 370 | border: 1px solid var(--border-color); /* 添加邊框 */ |
374 | 371 | border-radius: 6px; /* 圓角 */ |
375 | 372 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); /* 陰影效果 */ |
376 | 373 | margin: 2px; /* 增加微小間隔,讓按鍵更明顯 */ |
377 | 374 | } |
378 | 375 |
|
379 | | - /* ADDED: 點擊時的反白效果 */ |
| 376 | + /* 點擊時的反白效果 */ |
380 | 377 | #keyboard-overlay.emoji button:active { |
381 | 378 | background-color: var(--key-active-bg); /* 使用點擊時的顏色 #a0a0a0 */ |
382 | 379 | color: white; /* 讓表情符號更突出 */ |
|
441 | 438 | let cursorPosition = 0; |
442 | 439 | let preferredOffset = -1; |
443 | 440 |
|
444 | | - // --- ADDED: 簡繁切換狀態 --- |
| 441 | + // --- 簡繁切換狀態 --- |
445 | 442 | let isSimplifiedMode = false; // 預設為繁體模式 |
446 | 443 |
|
447 | 444 | // --- 長按相關狀態 --- |
|
480 | 477 | const KEYCODE_ENTER=0x0d; |
481 | 478 | const KEYCODE_SPACE=0x20; |
482 | 479 |
|
483 | | - // --- ADDED: Cursor and forward delete keycodes --- |
| 480 | + // --- Cursor and forward delete keycodes --- |
484 | 481 | const KEYCODE_HOME=0xff50; |
485 | 482 | const KEYCODE_LEFT=0xff51; |
486 | 483 | const KEYCODE_UP=0xff52; |
487 | 484 | const KEYCODE_RIGHT=0xff53; |
488 | 485 | const KEYCODE_DOWN=0xff54; |
489 | 486 | const KEYCODE_DEL_F=0xff; |
490 | 487 |
|
491 | | - // --- 簡繁轉換表 (將從 ts.txt 外部檔案載入,若失敗則為空) --- |
492 | | - let TS_MAP = {}; |
493 | | - |
494 | | - // 移除 FALLBACK_TS_MAP_RAW,完全依賴外部檔案載入。 |
495 | | - |
| 488 | + // --- 簡繁轉換表 (基礎示範用,完整轉換需要更大的字典) --- |
| 489 | + const TS_MAP = { |
| 490 | + '體': '体', '碼': '码', '說': '说', '這': '这', '個': '个', |
| 491 | + '們': '们', '樂': '乐', '風': '风', '開': '开', '對': '对', |
| 492 | + '會': '会', '國': '国', '議': '议', '變': '变', '應': '应', |
| 493 | + '學': '学', '發': '发', '廠': '厂', '廣': '广', '裡': '里', |
| 494 | + '幾': '几', '為': '为', '從': '从', '後': '后', '麵': '面', |
| 495 | + '術': '术', '隻': '只', '頭': '头', '話': '话', '裡': '里' |
| 496 | + }; |
| 497 | + |
496 | 498 | /** |
497 | 499 | * 執行簡體字轉換 |
498 | 500 | */ |
|
509 | 511 | return simplifiedText; |
510 | 512 | } |
511 | 513 |
|
512 | | - // --- 鍵盤配置 (略,與前版相同) --- |
| 514 | + // --- 鍵盤配置 --- |
513 | 515 | var Keyboards = { |
514 | 516 | english: { |
515 | 517 | shifted : false, |
|
607 | 609 | } |
608 | 610 |
|
609 | 611 | /** |
610 | | - * 鍵盤渲染函數 (略) |
| 612 | + * 鍵盤渲染函數 |
611 | 613 | */ |
612 | 614 | function drawKeyboard() { |
613 | 615 | const layout = currentLayout; |
|
752 | 754 | } |
753 | 755 | } |
754 | 756 |
|
| 757 | + // --- 浮層處理函數 --- |
| 758 | + |
755 | 759 | /** |
756 | 760 | * 處理表情符號/編輯器的浮層顯示與隱藏 |
757 | 761 | */ |
|
763 | 767 | div.style.display = 'none'; |
764 | 768 | div.innerHTML = ''; |
765 | 769 | delete div.dataset.show; |
766 | | - if (isShowing == show && div.className == type) return; |
| 770 | + // 如果是嘗試關閉當前開啟的浮層,則直接返回 |
| 771 | + if (isShowing == show && div.className == type) return; |
767 | 772 | } |
768 | 773 |
|
769 | 774 | if (show === false) return; |
|
886 | 891 | overlay("editor", 0); |
887 | 892 | } |
888 | 893 |
|
889 | | - // --- ADDED: 工具列邏輯 --- |
| 894 | + // --- 工具列邏輯 --- |
890 | 895 |
|
891 | 896 | function showToolbar() { |
892 | 897 | candidateDisplay.innerHTML = ''; |
893 | 898 |
|
894 | | - // 1. 如果浮層開啟 (Emoji/Editor) |
895 | | - if (overlayDiv.dataset.show) { |
896 | | - showToolbar(); |
897 | | - return; |
898 | | - } |
899 | | - |
900 | 899 | // **CHANGE: Replaced '🎤' with '簡/繁' toggle** |
901 | 900 | const toolbarItems = [ |
902 | 901 | {title: isSimplifiedMode ? "简" : "繁", tool:"1"}, // 簡繁切換鍵 |
|
920 | 919 | candidateDisplay.appendChild(fragment); |
921 | 920 | } |
922 | 921 |
|
| 922 | + /** |
| 923 | + * 處理工具列點擊事件 (已修正簡繁切換與浮層衝突) |
| 924 | + */ |
923 | 925 | function handleToolbarClick(target) { |
924 | 926 | const tool = target.dataset.tool; |
| 927 | + const isOverlayActive = !!overlayDiv.dataset.show; // 檢查是否有浮層正在開啟 |
| 928 | + |
| 929 | + // **修正**:如果浮層是開啟的,且點擊的是「簡繁切換鍵」(tool 1),則只切換模式並立即返回,不影響浮層狀態 |
| 930 | + if (isOverlayActive && tool === "1") { |
| 931 | + isSimplifiedMode = !isSimplifiedMode; |
| 932 | + console.log("TS mode switched while overlay active. Overlay preserved."); |
| 933 | + // 這裡不需要 showCandidates,因為浮層開啟時,下方的 candidateDisplay 應該保持清空或顯示工具列 |
| 934 | + // 但我們不希望它觸發重繪 (showCandidates) 導致浮層閃爍,因此直接 return |
| 935 | + return; |
| 936 | + } |
| 937 | + |
925 | 938 | if (tool === "1") { |
926 | | - // **CHANGE: Simplified/Traditional Mode Toggle** |
| 939 | + // Simplified/Traditional Mode Toggle |
927 | 940 | isSimplifiedMode = !isSimplifiedMode; |
928 | | - showCandidates(); // Redraw toolbar to update "簡" / "繁" label |
| 941 | + showCandidates(); // Redraw toolbar to update "简" / "繁" label |
929 | 942 | console.log("Mode switched to:", isSimplifiedMode ? "Simplified" : "Traditional"); |
930 | | - if (Object.keys(TS_MAP).length === 0 && isSimplifiedMode) { |
931 | | - alert("警告:簡繁轉換表 (ts.txt) 尚未載入或內容為空,轉換功能將不會生效。"); |
932 | | - } |
933 | 943 | } else if (tool === "2") { |
934 | 944 | overlay("emoji", 0); |
935 | 945 | } else if (tool === "3") { |
|
945 | 955 | } |
946 | 956 |
|
947 | 957 | /** |
948 | | - * 處理長按事件:提交輔助字符 (略) |
| 958 | + * 處理長按事件:提交輔助字符 |
949 | 959 | */ |
950 | 960 | function handleLongPress(keyElement, altChar) { |
951 | 961 | longPressCommitted = true; |
|
956 | 966 | } |
957 | 967 |
|
958 | 968 | /** |
959 | | - * 處理短按事件:執行編碼或普通字符輸入 (略) |
| 969 | + * 處理短按事件:執行編碼或普通字符輸入 |
960 | 970 | */ |
961 | 971 | function handleShortPress(keyElement) { |
962 | 972 | preferredOffset = -1; |
|
1110 | 1120 | } |
1111 | 1121 |
|
1112 | 1122 | /** |
1113 | | - * 統一的事件綁定函數 (略) |
| 1123 | + * 統一的事件綁定函數 |
1114 | 1124 | */ |
1115 | 1125 | function bindKeyEvents() { |
1116 | 1126 | document.querySelectorAll('.keyboard-key').forEach(keyElement => { |
|
1184 | 1194 | } |
1185 | 1195 |
|
1186 | 1196 | /** |
1187 | | - * 處理單字符輸入 (用於英文/符號模式或 Shift+字母) (略) |
| 1197 | + * 處理單字符輸入 (用於英文/符號模式或 Shift+字母) |
1188 | 1198 | */ |
1189 | 1199 | function handleCharInput(char) { |
1190 | 1200 | if (currentLayout.shifted) { |
|
1198 | 1208 | } |
1199 | 1209 | } |
1200 | 1210 |
|
1201 | | - // --- 核心輸入法邏輯 (其餘部分略) --- |
| 1211 | + // --- 核心輸入法邏輯 --- |
1202 | 1212 |
|
1203 | 1213 | var currentCode = ''; |
1204 | 1214 | var currentCandidates = []; |
|
1283 | 1293 | * 提交文字 (包含簡繁轉換邏輯) |
1284 | 1294 | */ |
1285 | 1295 | function commitText(textToCommit) { |
1286 | | - // **CHANGE: 在提交前進行簡繁轉換** |
| 1296 | + // 在提交前進行簡繁轉換 |
1287 | 1297 | const finalCommitText = convertToSimplified(textToCommit); |
1288 | 1298 |
|
1289 | 1299 | committedText = committedText.slice(0, cursorPosition) + finalCommitText + committedText.slice(cursorPosition); |
|
1304 | 1314 | resetInputState(); |
1305 | 1315 | } |
1306 | 1316 | } |
1307 | | - |
1308 | | - /** |
1309 | | - * 解析並載入簡繁轉換表 (ts.txt) |
1310 | | - * 預期檔案格式:繁體字\t簡體字 (一行一個) |
1311 | | - */ |
1312 | | - async function loadTSMap() { |
1313 | | - const fileName = './ts.txt'; |
1314 | | - let textToParse = ''; // 預設為空,如果載入失敗則保持為空 |
1315 | | - |
1316 | | - try { |
1317 | | - const response = await fetch(fileName); |
1318 | | - if (response.ok) { |
1319 | | - const responseText = await response.text(); |
1320 | | - // 檢查檔案內容是否有效,如果只有空白或註解,則不使用該內容 |
1321 | | - if (responseText.trim().replace(/^#.*$/gm, '').trim().length > 0) { |
1322 | | - textToParse = responseText; |
1323 | | - console.log(`成功載入簡繁轉換表: ${fileName}`); |
1324 | | - } else { |
1325 | | - console.warn(`載入外部簡繁轉換表: ${fileName} 成功,但內容為空或僅包含註解。`); |
1326 | | - } |
1327 | | - } else { |
1328 | | - console.error(`無法載入外部簡繁轉換表 (${response.status}),TS_MAP 將保持為空。`); |
1329 | | - } |
1330 | | - } catch (error) { |
1331 | | - console.error('外部簡繁轉換表讀取錯誤:', error.message, 'TS_MAP 將保持為空。'); |
1332 | | - } |
1333 | | - |
1334 | | - // 解析內容 |
1335 | | - const newTSMap = {}; |
1336 | | - if (textToParse.length > 0) { |
1337 | | - const lines = textToParse.trim().split('\n'); |
1338 | | - lines.forEach(line => { |
1339 | | - // 忽略註解行 |
1340 | | - if (line.startsWith('#') || line.trim() === '') return; |
1341 | | - |
1342 | | - // 使用 Tab 或多個空格分隔 |
1343 | | - const parts = line.split(/[\t\s]+/); |
1344 | | - |
1345 | | - if (parts.length >= 2) { |
1346 | | - const traditionalChar = parts[0].trim(); |
1347 | | - const simplifiedChar = parts[1].trim(); |
1348 | | - if (traditionalChar.length === 1 && simplifiedChar.length === 1) { |
1349 | | - newTSMap[traditionalChar] = simplifiedChar; |
1350 | | - } |
1351 | | - } |
1352 | | - }); |
1353 | | - } |
1354 | | - |
1355 | | - TS_MAP = newTSMap; |
1356 | | - console.log(`簡繁轉換表已載入,共 ${Object.keys(TS_MAP).length} 條轉換規則。`); |
1357 | | - } |
1358 | 1317 |
|
1359 | 1318 | function parseCinData(data) { |
1360 | 1319 | const newCodemap = {}; |
|
1385 | 1344 |
|
1386 | 1345 | async function loadSecondaryCodemap() { |
1387 | 1346 | const fileName = './3codes2.txt'; |
| 1347 | + // 這是備用的模擬編碼表 (用於測試,請確保您實際的 3codes2.txt 檔案存在並可讀取) |
1388 | 1348 | let textToParse = `# 這是備用的模擬編碼表 (用於測試)\n測\tqwe\n試\trty\n工\taaa\n夫\tqwe\n這\tqwe\n代\tqwe\n碼\tzxc\n說\tzxc\n裡\tqwe\n個\tzxc\n對\tqwe\n會\trty\n國\taaa\n議\tqwe\n變\tqwe\n應\tqwe\n學\trty\n發\taaa\n廠\tqwe\n廣\tqwe\n裡\tqwe\n幾\tqwe\n為\trty\n從\taaa\n後\tqwe\n麵\tqwe\n術\tqwe\n隻\trty\n頭\taaa\n話\tqwe\n裡\tqwe\n「\t;'\n」\t';\n`; |
1389 | 1349 | let statusMessage = '編碼表載入中...'; |
1390 | 1350 |
|
|
1394 | 1354 | throw new Error(`無法載入外部檔案 (${response.status})。`); |
1395 | 1355 | } |
1396 | 1356 | const responseText = await response.text(); |
| 1357 | + // 檢查是否真的讀到了有用的內容,否則使用備用內容 |
1397 | 1358 | if (responseText.trim().replace(/^#.*$/gm, '').trim().length > 0) { |
1398 | 1359 | textToParse = responseText; |
1399 | 1360 | } |
|
1503 | 1464 |
|
1504 | 1465 | currentCandidates.forEach((candidate, index) => { |
1505 | 1466 | const span = document.createElement('span'); |
1506 | | - // **NOTE: 候選字在顯示時不轉換,僅在選字 commitText 時才轉換。** |
| 1467 | + // NOTE: 候選字在顯示時不轉換,僅在選字 commitText 時才轉換。 |
1507 | 1468 | span.textContent = candidate; |
1508 | 1469 | span.onclick = () => selectCandidate(index); |
1509 | 1470 | candidateDisplay.appendChild(span); |
|
1528 | 1489 | updateOutputDisplay(); |
1529 | 1490 | } |
1530 | 1491 |
|
1531 | | - // --- 工具列與查碼功能 (略) --- |
| 1492 | + // --- 工具列與查碼功能 --- |
1532 | 1493 |
|
1533 | 1494 | function copyTextToClipboard() { |
1534 | 1495 | const textToCopy = committedText; |
|
1680 | 1641 | // 頁面啟動 |
1681 | 1642 | document.addEventListener('DOMContentLoaded', () => { |
1682 | 1643 | drawKeyboard(); |
1683 | | - loadTSMap(); // **重要:完全依賴外部檔案載入** |
1684 | 1644 | loadSecondaryCodemap(); |
1685 | 1645 |
|
1686 | 1646 | textOutput.addEventListener('click', handleTextTap); |
|
0 commit comments