|
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 | | - /* 手機優化樣式,參考 keyboard.css 和 index-0.html 樣式 */ |
| 12 | + /* 手機優化樣式 */ |
13 | 13 | /* ----------------------------------------------------- */ |
14 | 14 |
|
15 | 15 | :root { |
|
28 | 28 | margin: 0; |
29 | 29 | padding: 0; |
30 | 30 | box-sizing: border-box; |
31 | | - -webkit-tap-highlight-color: transparent; |
| 31 | + -webkit-tap-highlight-color: transparent; /* 移除點擊高亮 */ |
32 | 32 | outline: none; |
33 | 33 | } |
34 | 34 |
|
|
99 | 99 | cursor: text; |
100 | 100 | } |
101 | 101 |
|
102 | | - /* 候選字與輸入區容器 */ |
| 102 | + /* 候選字與輸入區 */ |
103 | 103 | #input-area { |
104 | 104 | width: 100%; |
105 | 105 | background-color: var(--container-bg); |
|
110 | 110 | border: 1px solid var(--border-color); |
111 | 111 | } |
112 | 112 |
|
113 | | - /* 編碼輸入顯示 */ |
114 | 113 | #main-input { |
115 | 114 | width: 100%; |
116 | 115 | padding: 8px; |
|
125 | 124 | overflow-x: auto; |
126 | 125 | } |
127 | 126 |
|
128 | | - /* 候選字橫向滾動容器 */ |
| 127 | + /* === 候選字區域:修改為橫向滑動 === */ |
129 | 128 | #candidate-display { |
130 | | - display: flex; |
| 129 | + display: flex; /* 確保內容在同一行 */ |
131 | 130 | align-items: center; |
132 | 131 | padding: 8px; |
133 | | - gap: 12px; |
134 | | - white-space: nowrap; /* 確保所有候選字在一行 */ |
| 132 | + gap: 12px; /* 增加間距 */ |
| 133 | + white-space: nowrap; /* 內容不換行 */ |
135 | 134 | overflow-x: scroll; /* 允許橫向滾動 */ |
136 | | - -webkit-overflow-scrolling: touch; /* 提升 iOS 上的滾動體驗 */ |
| 135 | + -webkit-overflow-scrolling: touch; /* 改善 iOS 上的滑動體驗 */ |
137 | 136 | min-height: 40px; |
138 | 137 | } |
139 | 138 |
|
| 139 | + /* 隱藏滾動條 */ |
140 | 140 | #candidate-display::-webkit-scrollbar { |
141 | | - display: none; /* 隱藏滾動條 */ |
| 141 | + display: none; |
142 | 142 | } |
143 | 143 |
|
144 | 144 | #candidate-display span { |
|
148 | 148 | transition: all 0.2s; |
149 | 149 | user-select: none; |
150 | 150 | white-space: nowrap; |
151 | | - font-size: 1.2rem; /* 略微放大字體 */ |
152 | | - flex-shrink: 0; /* 防止候選字被壓縮 */ |
| 151 | + font-size: 1.2rem; /* 稍大一點的字體 */ |
| 152 | + flex-shrink: 0; /* 確保文字不被壓縮 */ |
| 153 | + text-align: center; |
153 | 154 | } |
154 | 155 |
|
155 | 156 | #candidate-display span:active, #candidate-display span:hover { |
|
190 | 191 |
|
191 | 192 | .key { |
192 | 193 | flex-grow: 1; |
193 | | - flex-basis: 0; |
| 194 | + flex-basis: 0; |
194 | 195 | padding: 0; |
195 | 196 | border-radius: 6px; |
196 | 197 | background-color: var(--key-bg); |
|
199 | 200 | user-select: none; |
200 | 201 | transition: background-color 0.1s; |
201 | 202 | box-shadow: 0 2px 3px rgba(0, 0, 0, 0.2); |
202 | | - line-height: 50px; |
| 203 | + line-height: 50px; |
203 | 204 | min-height: 50px; |
204 | 205 | font-size: 1.2rem; |
205 | 206 | font-weight: bold; |
|
213 | 214 | } |
214 | 215 |
|
215 | 216 | .key.space-key { |
216 | | - flex: 2; /* 兩倍寬 */ |
| 217 | + flex: 2; |
217 | 218 | background-color: #c5cae9; |
218 | 219 | } |
219 | 220 |
|
|
246 | 247 | </div> |
247 | 248 |
|
248 | 249 | <script> |
249 | | - // --- 資料處理與解析 (從 index-0.html 擷取核心部分) --- |
| 250 | + // --- 資料處理與解析 --- |
250 | 251 | let currentCodemap = {}; |
| 252 | + let currentReverseCodemap = {}; |
251 | 253 |
|
252 | 254 | const keyname_map_upper = { |
253 | | - 'a': '一', 'b': '工', 'c': '人', 'd': '心', 'e': '大', 'f': '手', 'g': '木', |
254 | | - 'h': '土', 'i': '口', 'j': '女', 'k': '艸', 'l': '田', 'm': '山', 'n': 'P', |
255 | | - 'o': '金', 'p': '月', 'q': '水', 'r': '日', 's': '竹', 't': '禾', 'u': '卜', |
256 | | - 'v': '戈', 'w': 'O', 'x': 'X', 'y': 'Y', 'z': 'Z', |
257 | | - // 符號對應 |
258 | | - "'": "〃", ';': ';', ',': ',', '.': '.', '/': '/', ' ': '空白', 'enter': '換行', 'backspace': '刪除', 'shift': '中/英', |
| 255 | + 'a': 'A', 'b': 'B', 'c': 'C', 'd': 'D', 'e': 'E', 'f': 'F', 'g': 'G', |
| 256 | + 'h': 'H', 'i': 'I', 'j': 'J', 'k': 'K', 'l': 'L', 'm': 'M', 'n': 'N', |
| 257 | + 'o': 'O', 'p': 'P', 'q': 'Q', 'r': 'R', 's': 'S', 't': 'T', 'u': 'U', |
| 258 | + 'v': 'V', 'w': 'W', 'x': 'X', 'y': 'Y', 'z': 'Z', |
| 259 | + // 符號對應 (按鍵顯示) |
| 260 | + "'": "〃", ';': ';', ',': ',', '.': '.', '/': '/', ' ': '空白鍵', 'enter': 'Enter', 'backspace': '刪除鍵', 'shift': '中/英', |
259 | 261 | }; |
260 | 262 | const keyname_map_lower = { |
261 | 263 | 'a': 'a', 'b': 'b', 'c': 'c', 'd': 'd', 'e': 'e', 'f': 'f', 'g': 'g', |
262 | 264 | 'h': 'h', 'i': 'i', 'j': 'j', 'k': 'k', 'l': 'l', 'm': 'm', 'n': 'n', |
263 | 265 | 'o': 'o', 'p': 'p', 'q': 'q', 'r': 'r', 's': 's', 't': 't', 'u': 'u', |
264 | 266 | 'v': 'v', 'w': 'w', 'x': 'x', 'y': 'y', 'z': 'z', |
265 | | - // 符號對應 |
266 | | - "'": "'", ';': ';', ',': ',', '.': '.', '/': '/', ' ': ' ', 'enter': 'Enter', 'backspace': 'Back', 'shift': 'Shift', |
| 267 | + "'": "'", ';': ';', ',': ',', '.': '.', '/': '/', ' ': ' ', 'enter': 'Enter', 'backspace': '刪除鍵', 'shift': '中/英', |
267 | 268 | }; |
268 | 269 |
|
269 | 270 | // DOM 元素 |
|
278 | 279 | const MAX_CODE_LENGTH = 3; |
279 | 280 | let currentCode = ''; |
280 | 281 | let currentCandidates = []; |
| 282 | + // let currentPage = 0; // 已移除分頁邏輯 |
| 283 | + // const candidatesPerPage = 10; // 已移除分頁邏輯 |
281 | 284 | let isEnglishMode = false; |
282 | 285 |
|
283 | | - // 函式:格式化碼表資料為物件 (從 index-0.html 擷取) |
284 | 286 | function parseCinData(data) { |
285 | 287 | const newCodemap = {}; |
| 288 | + const newReverseCodemap = {}; |
286 | 289 | const lines = data.trim().split('\n'); |
287 | 290 | lines.forEach(line => { |
288 | 291 | const parts = line.split('\t'); |
289 | 292 | if (parts.length >= 2 && !line.startsWith('#')) { |
290 | | - const code = parts[0].trim().toLowerCase(); // 確保編碼小寫 |
| 293 | + const code = parts[0].trim(); |
291 | 294 | const char = parts[1].trim(); |
292 | 295 | if (code && char) { |
293 | 296 | if (!newCodemap[code]) { |
294 | 297 | newCodemap[code] = []; |
295 | 298 | } |
296 | 299 | newCodemap[code].push(char); |
| 300 | + |
| 301 | + if (!newReverseCodemap[char]) { |
| 302 | + newReverseCodemap[char] = []; |
| 303 | + } |
| 304 | + newReverseCodemap[char].push(code); |
297 | 305 | } |
298 | 306 | } |
299 | 307 | }); |
300 | | - return newCodemap; |
| 308 | + return { newCodemap, newReverseCodemap }; |
301 | 309 | } |
302 | 310 |
|
303 | | - // 函式:載入次常用字碼表 (僅載入 3codes2.txt) |
304 | 311 | async function loadSecondaryCodemap() { |
305 | 312 | const fileName = './3codes2.txt'; |
306 | 313 | try { |
|
309 | 316 | throw new Error('Network response was not ok'); |
310 | 317 | } |
311 | 318 | const text = await response.text(); |
312 | | - currentCodemap = parseCinData(text); |
| 319 | + const { newCodemap, newReverseCodemap } = parseCinData(text); |
| 320 | + currentCodemap = newCodemap; |
| 321 | + currentReverseCodemap = newReverseCodemap; |
313 | 322 | updateModeDisplay(); |
314 | 323 | mainInput.textContent = '編碼表載入成功'; |
315 | 324 | } catch (error) { |
316 | 325 | console.error(`載入 ${fileName} 時發生錯誤:`, error); |
317 | | - mainInput.textContent = `錯誤:無法載入編碼檔 ${fileName}。請確認檔案存在且與此 HTML 位於相同目錄下。`; |
| 326 | + mainInput.textContent = `錯誤:無法載入編碼檔 ${fileName}。請確認檔案存在。`; |
318 | 327 | } |
319 | 328 | resetInputState(); |
320 | 329 | } |
321 | 330 |
|
322 | 331 | // --- 處理中文模式輸入的核心函數 --- |
323 | 332 | function handleChineseInput(code) { |
324 | | - |
| 333 | + // 忽略數字鍵 |
| 334 | + if (code.match(/^[1-90]$/)) { |
| 335 | + return; |
| 336 | + } |
| 337 | + |
325 | 338 | if (code === 'backspace') { |
326 | 339 | if (currentCode.length > 0) { |
327 | 340 | currentCode = currentCode.slice(0, -1); |
|
332 | 345 | return; |
333 | 346 | } |
334 | 347 | if (code === 'enter') { |
335 | | - // 如果有候選字,選第一個上字 |
336 | 348 | if (currentCandidates.length > 0) { |
337 | | - selectCandidate(0); |
| 349 | + selectCandidate(0); // 選第一個候選字上字 |
338 | 350 | } else { |
339 | 351 | textOutput.textContent += '\n'; // 換行 |
340 | 352 | } |
|
343 | 355 |
|
344 | 356 | if (code === ' ') { |
345 | 357 | if (currentCandidates.length > 0) { |
346 | | - selectCandidate(0); |
| 358 | + selectCandidate(0); // 選第一個候選字上字 |
347 | 359 | } else { |
348 | 360 | textOutput.textContent += ' '; // 輸出全形空格 |
349 | 361 | } |
350 | 362 | return; |
351 | 363 | } |
352 | 364 |
|
353 | 365 | const inputChar = code.toLowerCase(); |
354 | | - // 接受所有 26 字母和 5 符號的輸入: a-z, ; ' , . / |
| 366 | + // 接受所有 26 字母和 5 符號的輸入 |
355 | 367 | if (inputChar.match(/^[a-z;'./,<>]$/)) { |
356 | 368 | if (currentCode.length === MAX_CODE_LENGTH) { |
357 | 369 | if (currentCandidates.length > 0) { |
|
391 | 403 |
|
392 | 404 | // --- 核心狀態更新與顯示函數 (中文模式專用) --- |
393 | 405 | function updateStateAndDisplay() { |
394 | | - mainInput.textContent = `輸入編碼: ${formatCode(currentCode)}`; |
| 406 | + mainInput.textContent = formatCode(currentCode); |
395 | 407 | currentCandidates = currentCodemap[currentCode] || []; |
396 | 408 |
|
397 | 409 | if (currentCode.length === MAX_CODE_LENGTH) { |
|
400 | 412 | resetInputState(); |
401 | 413 | return; |
402 | 414 | } else if (currentCandidates.length === 0) { |
403 | | - // 沒有候選字,清空輸入 |
| 415 | + // 如果沒有候選字,清空輸入,不做任何輸出 |
404 | 416 | resetInputState(); |
405 | 417 | return; |
406 | 418 | } |
|
409 | 421 | showCandidates(); |
410 | 422 | } |
411 | 423 |
|
412 | | - // --- 顯示候選字為可滑動列表 (更新) --- |
| 424 | + // --- 顯示候選字為**滑動列表 (已移除編號和分頁)** --- |
413 | 425 | function showCandidates() { |
414 | 426 | candidateDisplay.innerHTML = ''; |
415 | 427 |
|
|
421 | 433 | return; |
422 | 434 | } |
423 | 435 |
|
424 | | - // 顯示所有候選字並支援點擊 |
| 436 | + // 顯示所有候選字 |
425 | 437 | currentCandidates.forEach((candidate, index) => { |
426 | 438 | const span = document.createElement('span'); |
427 | | - // 移除數字標記,直接顯示漢字 |
428 | | - span.textContent = candidate; |
429 | | - span.onclick = () => selectCandidate(index); |
| 439 | + // 不顯示編號 |
| 440 | + span.textContent = candidate; |
| 441 | + // 直接使用陣列索引 |
| 442 | + span.onclick = () => selectCandidate(index); |
430 | 443 | candidateDisplay.appendChild(span); |
431 | 444 | }); |
| 445 | + |
| 446 | + // 確保候選字滾動到最左邊 |
| 447 | + candidateDisplay.scrollLeft = 0; |
432 | 448 | } |
433 | 449 |
|
434 | | - // --- 選擇候選字並清空輸入 --- |
| 450 | + // --- 選擇候選字並清空輸入 (已移除分頁邏輯) --- |
435 | 451 | function selectCandidate(index) { |
436 | | - const selectedCandidate = currentCandidates[index]; |
| 452 | + // 使用絕對索引 |
| 453 | + const selectedCandidate = currentCandidates[index]; |
437 | 454 | if (selectedCandidate) { |
438 | 455 | textOutput.textContent += selectedCandidate; |
439 | 456 | resetInputState(); |
|
444 | 461 | function resetInputState() { |
445 | 462 | currentCode = ''; |
446 | 463 | currentCandidates = []; |
447 | | - mainInput.textContent = isEnglishMode ? '英文模式' : '編碼顯示在此'; |
| 464 | + mainInput.textContent = '編碼顯示在此'; |
448 | 465 | showCandidates(); |
449 | 466 | } |
450 | 467 |
|
451 | 468 | // --- 格式化編碼顯示 --- |
452 | 469 | function formatCode(code) { |
453 | 470 | let displayString = ''; |
454 | 471 | for (const char of code) { |
455 | | - // 顯示基礎字根/符號 |
| 472 | + // 使用大寫字母/全形符號顯示編碼 |
456 | 473 | displayString += keyname_map_upper[char] || char.toUpperCase(); |
457 | 474 | } |
458 | 475 | return displayString; |
|
464 | 481 | document.querySelectorAll('.key[data-code]').forEach(keyElement => { |
465 | 482 | const code = keyElement.dataset.code; |
466 | 483 | if (map[code]) { |
467 | | - // 功能鍵使用原始標籤,字符鍵使用大小寫/字根 |
468 | 484 | keyElement.textContent = map[code]; |
469 | 485 | } |
470 | 486 | }); |
|
477 | 493 |
|
478 | 494 | // --- 動態生成手機優化鍵盤 --- |
479 | 495 | function generateMobileKeyboard() { |
480 | | - // 佈局包含 26 字母和 5 個特殊符號: ; ' , . / |
481 | 496 | const layout_mobile = [ |
482 | 497 | ['Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P'], |
483 | 498 | ['A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ';'], |
484 | | - ['Shift', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', ',', '.', '/', "'"], |
485 | | - ['Space', 'Back', 'Enter'] // 移除 123 |
| 499 | + ['Z', 'X', 'C', 'V', 'B', 'N', 'M', ',', '.', '/', "'"], |
| 500 | + ['Shift', 'Space', 'Back', 'Enter'] |
486 | 501 | ]; |
487 | 502 |
|
488 | 503 | keyboardContainer.innerHTML = ''; |
|
545 | 560 | return; |
546 | 561 | } |
547 | 562 |
|
548 | | - // 嘗試使用現代瀏覽器的 Clipboard API |
549 | 563 | if (navigator.clipboard) { |
550 | 564 | navigator.clipboard.writeText(textToCopy).then(() => { |
551 | 565 | alert('文字已成功複製到剪貼簿!'); |
|
554 | 568 | alert('複製失敗,可能需要 HTTPS 或手動選取。'); |
555 | 569 | }); |
556 | 570 | } else { |
557 | | - // 後備方案 |
558 | 571 | const tempInput = document.createElement('textarea'); |
559 | 572 | tempInput.value = textToCopy; |
560 | 573 | document.body.appendChild(tempInput); |
|
582 | 595 | // 頁面啟動 |
583 | 596 | document.addEventListener('DOMContentLoaded', () => { |
584 | 597 | generateMobileKeyboard(); |
585 | | - loadSecondaryCodemap(); // 啟動時載入次常用字碼表 |
| 598 | + loadSecondaryCodemap(); |
586 | 599 |
|
587 | 600 | copyButton.addEventListener('click', copyTextToClipboard); |
588 | 601 | clearButton.addEventListener('click', clearOutput); |
|
0 commit comments