js_lis 2.0.6 → 2.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/VirtualKeyboard.js +157 -189
- package/layouts.js +2 -2
- package/package.json +1 -1
- package/style/kbstyle.js +1 -1
package/VirtualKeyboard.js
CHANGED
|
@@ -14,35 +14,16 @@ export class VirtualKeyboard {
|
|
|
14
14
|
this.shiftActive = false;
|
|
15
15
|
this.capsLockActive = false;
|
|
16
16
|
this.isScrambled = false;
|
|
17
|
-
this.isVirtualKeyboardActive = false;
|
|
17
|
+
this.isVirtualKeyboardActive = false;
|
|
18
18
|
|
|
19
|
-
// Maintain plaintext buffers per input (not stored in DOM). DOM will store only encrypted text.
|
|
20
19
|
this.inputPlaintextBuffers = new WeakMap();
|
|
21
|
-
|
|
22
|
-
// Security state
|
|
23
|
-
// this.security = {
|
|
24
|
-
// detectorStatus: "idle",
|
|
25
|
-
// suspiciousEvents: [],
|
|
26
|
-
// observer: null,
|
|
27
|
-
// addSuspicious(event) {
|
|
28
|
-
// try {
|
|
29
|
-
// this.suspiciousEvents.push({
|
|
30
|
-
// time: new Date().toISOString(),
|
|
31
|
-
// ...event,
|
|
32
|
-
// });
|
|
33
|
-
// console.warn("[VK Security] Suspicious activity detected:", event);
|
|
34
|
-
// } catch (_) {}
|
|
35
|
-
// },
|
|
36
|
-
// };
|
|
37
|
-
|
|
38
|
-
injectCSS();
|
|
20
|
+
|
|
39
21
|
this.initialize();
|
|
40
22
|
}
|
|
41
23
|
|
|
42
24
|
async initialize() {
|
|
43
25
|
try {
|
|
44
|
-
|
|
45
|
-
// this.startKeyloggerDetector();
|
|
26
|
+
injectCSS();
|
|
46
27
|
this.render();
|
|
47
28
|
this.initializeInputListeners();
|
|
48
29
|
console.log("VirtualKeyboard initialized successfully.");
|
|
@@ -51,113 +32,6 @@ export class VirtualKeyboard {
|
|
|
51
32
|
}
|
|
52
33
|
}
|
|
53
34
|
|
|
54
|
-
// Inject CSP Report-Only meta for runtime visibility into potential violations
|
|
55
|
-
// applyCSPReportOnly() {
|
|
56
|
-
// // Disabled: Report-Only CSP must be set via HTTP headers. See server middleware in app.js.
|
|
57
|
-
// return;
|
|
58
|
-
// }
|
|
59
|
-
|
|
60
|
-
// Start MutationObserver and limited listener-hook to detect potential keylogger injections
|
|
61
|
-
// startKeyloggerDetector() {
|
|
62
|
-
// try {
|
|
63
|
-
// const allowlistedScriptHosts = new Set([
|
|
64
|
-
// window.location.host,
|
|
65
|
-
// "cdnjs.cloudflare.com",
|
|
66
|
-
// ]);
|
|
67
|
-
|
|
68
|
-
// const isSuspiciousScript = (node) => {
|
|
69
|
-
// try {
|
|
70
|
-
// if (!(node instanceof HTMLScriptElement)) return false;
|
|
71
|
-
// const src = node.getAttribute("src") || "";
|
|
72
|
-
// if (!src) {
|
|
73
|
-
// const code = (node.textContent || "").toLowerCase();
|
|
74
|
-
// return /addEventListener\(['\"]key|document\.onkey|keylogger|send\(|fetch\(|xmlhttprequest/i.test(code);
|
|
75
|
-
// }
|
|
76
|
-
// const url = new URL(src, window.location.href);
|
|
77
|
-
// return !Array.from(allowlistedScriptHosts).some((h) => url.host.endsWith(h));
|
|
78
|
-
// } catch (_) {
|
|
79
|
-
// return true;
|
|
80
|
-
// }
|
|
81
|
-
// };
|
|
82
|
-
|
|
83
|
-
// const isSuspiciousElement = (node) => {
|
|
84
|
-
// try {
|
|
85
|
-
// if (!(node instanceof Element)) return false;
|
|
86
|
-
// const hasKeyHandlers = node.hasAttribute("onkeydown") || node.hasAttribute("onkeypress") || node.hasAttribute("onkeyup");
|
|
87
|
-
// const hiddenFrame = node.tagName === "IFRAME" && (node.getAttribute("sandbox") === null || node.getAttribute("srcdoc") !== null);
|
|
88
|
-
// return hasKeyHandlers || hiddenFrame;
|
|
89
|
-
// } catch (_) {
|
|
90
|
-
// return false;
|
|
91
|
-
// }
|
|
92
|
-
// };
|
|
93
|
-
|
|
94
|
-
// const quarantineScript = (script) => {
|
|
95
|
-
// try {
|
|
96
|
-
// script.type = "text/plain";
|
|
97
|
-
// script.setAttribute("data-quarantined", "true");
|
|
98
|
-
// } catch (_) {}
|
|
99
|
-
// };
|
|
100
|
-
|
|
101
|
-
// const observer = new MutationObserver((mutations) => {
|
|
102
|
-
// for (const m of mutations) {
|
|
103
|
-
// if (m.type === "childList") {
|
|
104
|
-
// m.addedNodes.forEach((node) => {
|
|
105
|
-
// if (isSuspiciousScript(node)) {
|
|
106
|
-
// this.security.addSuspicious({ type: "script", reason: "suspicious source or inline pattern", node });
|
|
107
|
-
// quarantineScript(node);
|
|
108
|
-
// } else if (isSuspiciousElement(node)) {
|
|
109
|
-
// this.security.addSuspicious({ type: "element", reason: "inline key handlers or risky frame", node });
|
|
110
|
-
// }
|
|
111
|
-
// });
|
|
112
|
-
// } else if (m.type === "attributes" && isSuspiciousElement(m.target)) {
|
|
113
|
-
// this.security.addSuspicious({ type: "attr", reason: `attribute ${m.attributeName}`, node: m.target });
|
|
114
|
-
// }
|
|
115
|
-
// }
|
|
116
|
-
// });
|
|
117
|
-
|
|
118
|
-
// observer.observe(document.documentElement, {
|
|
119
|
-
// childList: true,
|
|
120
|
-
// subtree: true,
|
|
121
|
-
// attributes: true,
|
|
122
|
-
// attributeFilter: ["onkeydown", "onkeypress", "onkeyup", "src", "type", "sandbox", "srcdoc"],
|
|
123
|
-
// });
|
|
124
|
-
|
|
125
|
-
// // Hook addEventListener to monitor keyboard listeners registration
|
|
126
|
-
// const OriginalAddEventListener = EventTarget.prototype.addEventListener;
|
|
127
|
-
// const selfRef = this;
|
|
128
|
-
// EventTarget.prototype.addEventListener = function(type, listener, options) {
|
|
129
|
-
// try {
|
|
130
|
-
// if (type && /^(key(?:down|up|press))$/i.test(type)) {
|
|
131
|
-
// const info = {
|
|
132
|
-
// type: "event-listener",
|
|
133
|
-
// reason: `keyboard listener registered: ${type}`,
|
|
134
|
-
// target: this,
|
|
135
|
-
// };
|
|
136
|
-
// selfRef.security.addSuspicious(info);
|
|
137
|
-
// }
|
|
138
|
-
// } catch (_) {}
|
|
139
|
-
// return OriginalAddEventListener.call(this, type, listener, options);
|
|
140
|
-
// };
|
|
141
|
-
|
|
142
|
-
// this.security.observer = observer;
|
|
143
|
-
// this.security.detectorStatus = "running";
|
|
144
|
-
|
|
145
|
-
// // Expose minimal security API
|
|
146
|
-
// window.VKSecurity = {
|
|
147
|
-
// getStatus: () => this.security.detectorStatus,
|
|
148
|
-
// getEvents: () => [...this.security.suspiciousEvents],
|
|
149
|
-
// stop: () => {
|
|
150
|
-
// try { observer.disconnect(); } catch (_) {}
|
|
151
|
-
// this.security.detectorStatus = "stopped";
|
|
152
|
-
// },
|
|
153
|
-
// };
|
|
154
|
-
// console.info("[VK Security] Keylogger detector started");
|
|
155
|
-
// } catch (err) {
|
|
156
|
-
// this.security.detectorStatus = "error";
|
|
157
|
-
// console.warn("[VK Security] Detector error:", err);
|
|
158
|
-
// }
|
|
159
|
-
// }
|
|
160
|
-
|
|
161
35
|
getLayoutName(layout) {
|
|
162
36
|
switch (layout) {
|
|
163
37
|
case "full":
|
|
@@ -170,6 +44,8 @@ export class VirtualKeyboard {
|
|
|
170
44
|
return "Numpad Keyboard";
|
|
171
45
|
case "symbols":
|
|
172
46
|
return "Symbols Keyboard";
|
|
47
|
+
case "full-layouts":
|
|
48
|
+
return "Full English + Numpad";
|
|
173
49
|
default:
|
|
174
50
|
return "Unknown Layout";
|
|
175
51
|
}
|
|
@@ -179,13 +55,8 @@ export class VirtualKeyboard {
|
|
|
179
55
|
document.addEventListener("click", (e) => {
|
|
180
56
|
const target = e.target;
|
|
181
57
|
if (target.tagName === "INPUT" || target.tagName === "TEXTAREA") {
|
|
182
|
-
// Store the cursor position immediately after click
|
|
183
58
|
const clickCursorPos = target.selectionStart;
|
|
184
|
-
|
|
185
|
-
// Don't move cursor to end if virtual keyboard is currently active
|
|
186
59
|
this.setCurrentInput(target, !this.isVirtualKeyboardActive);
|
|
187
|
-
|
|
188
|
-
// Restore the cursor position that user clicked to set
|
|
189
60
|
setTimeout(() => {
|
|
190
61
|
if (target.selectionStart !== clickCursorPos) {
|
|
191
62
|
target.setSelectionRange(clickCursorPos, clickCursorPos);
|
|
@@ -194,12 +65,9 @@ export class VirtualKeyboard {
|
|
|
194
65
|
}
|
|
195
66
|
});
|
|
196
67
|
|
|
197
|
-
document.addEventListener(
|
|
198
|
-
"focus",
|
|
199
|
-
(e) => {
|
|
68
|
+
document.addEventListener("focus", (e) => {
|
|
200
69
|
const target = e.target;
|
|
201
70
|
if (target.tagName === "INPUT" || target.tagName === "TEXTAREA") {
|
|
202
|
-
// Don't move cursor to end if virtual keyboard is currently active
|
|
203
71
|
this.setCurrentInput(target, !this.isVirtualKeyboardActive);
|
|
204
72
|
}
|
|
205
73
|
},
|
|
@@ -210,21 +78,28 @@ export class VirtualKeyboard {
|
|
|
210
78
|
document.addEventListener("input", (e) => {
|
|
211
79
|
const target = e.target;
|
|
212
80
|
if ((target.tagName === "INPUT" || target.tagName === "TEXTAREA") && target === this.currentInput) {
|
|
213
|
-
// Only handle real keyboard input if it's not coming from virtual keyboard operations
|
|
214
81
|
if (!this.isVirtualKeyboardActive) {
|
|
215
|
-
this.handleRealKeyboardInput(target);
|
|
82
|
+
this.handleRealKeyboardInput(target, e);
|
|
216
83
|
}
|
|
217
84
|
}
|
|
218
85
|
});
|
|
219
86
|
|
|
220
|
-
// Add real keyboard
|
|
87
|
+
// Add real keyboard keydown listener for proper character tracking
|
|
221
88
|
document.addEventListener("keydown", (e) => {
|
|
222
89
|
const target = e.target;
|
|
223
90
|
if ((target.tagName === "INPUT" || target.tagName === "TEXTAREA") && target === this.currentInput) {
|
|
224
|
-
//
|
|
91
|
+
// Reset the virtual keyboard flag when real keyboard is used
|
|
92
|
+
if (!['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End'].includes(e.code)) {
|
|
93
|
+
this.isVirtualKeyboardActive = false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Handle real keyboard input
|
|
97
|
+
if (!this.isVirtualKeyboardActive) {
|
|
98
|
+
this.handleRealKeyboardKeydown(target, e);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Handle arrow keys for all input types
|
|
225
102
|
if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End'].includes(e.code)) {
|
|
226
|
-
// Let the browser handle the navigation naturally
|
|
227
|
-
// Just ensure the input stays focused
|
|
228
103
|
setTimeout(() => {
|
|
229
104
|
target.focus();
|
|
230
105
|
}, 0);
|
|
@@ -246,7 +121,6 @@ export class VirtualKeyboard {
|
|
|
246
121
|
this.currentInput = inputElement;
|
|
247
122
|
this.currentInput.classList.add("keyboard-active");
|
|
248
123
|
|
|
249
|
-
// Ensure the input is focused and cursor is visible
|
|
250
124
|
this.currentInput.focus();
|
|
251
125
|
|
|
252
126
|
// Ensure buffer initialized for this input
|
|
@@ -293,6 +167,7 @@ export class VirtualKeyboard {
|
|
|
293
167
|
|
|
294
168
|
const layoutSelector = document.createElement("select");
|
|
295
169
|
layoutSelector.id = "layout-selector";
|
|
170
|
+
layoutSelector.setAttribute("aria-label", "Select keyboard layout"); // ✅ เพิ่มบรรทัดนี้
|
|
296
171
|
layoutSelector.onchange = (e) => this.changeLayout(e.target.value);
|
|
297
172
|
|
|
298
173
|
const layouts = ["full", "en", "th", "numpad", "symbols"];
|
|
@@ -305,6 +180,7 @@ export class VirtualKeyboard {
|
|
|
305
180
|
layoutSelector.value = this.currentLayout;
|
|
306
181
|
controlsContainer.appendChild(layoutSelector);
|
|
307
182
|
|
|
183
|
+
|
|
308
184
|
keyboard.appendChild(controlsContainer);
|
|
309
185
|
|
|
310
186
|
const layout = this.layouts[this.currentLayout];
|
|
@@ -325,13 +201,14 @@ export class VirtualKeyboard {
|
|
|
325
201
|
keyElement.classList.add("concat-keys");
|
|
326
202
|
}
|
|
327
203
|
|
|
328
|
-
if (key === "
|
|
204
|
+
if (key === " ") {
|
|
329
205
|
keyElement.className += " space";
|
|
206
|
+
keyElement.innerHTML = 'ฺ';
|
|
330
207
|
}
|
|
331
208
|
|
|
332
209
|
if (key === "backspace" || key === "Backspace") {
|
|
333
210
|
keyElement.className += " backspacew";
|
|
334
|
-
keyElement.innerHTML = '<i class="fa fa-backspace"></i
|
|
211
|
+
keyElement.innerHTML = '<i class="fa fa-backspace"></i>ฺ';
|
|
335
212
|
}
|
|
336
213
|
|
|
337
214
|
keyElement.onclick = (e) => {
|
|
@@ -434,6 +311,20 @@ export class VirtualKeyboard {
|
|
|
434
311
|
key.classList.toggle("active", this.isScrambled);
|
|
435
312
|
});
|
|
436
313
|
}
|
|
314
|
+
|
|
315
|
+
if (this.currentLayout === "full") {
|
|
316
|
+
if (this.isScrambled) {
|
|
317
|
+
this.unscrambleKeys();
|
|
318
|
+
} else {
|
|
319
|
+
this.scrambleFullLayoutKeys();
|
|
320
|
+
}
|
|
321
|
+
this.isScrambled = !this.isScrambled;
|
|
322
|
+
document
|
|
323
|
+
.querySelectorAll('.key[data-key="scrambled"]')
|
|
324
|
+
.forEach((key) => {
|
|
325
|
+
key.classList.toggle("active", this.isScrambled);
|
|
326
|
+
});
|
|
327
|
+
}
|
|
437
328
|
this.isVirtualKeyboardActive = false;
|
|
438
329
|
return;
|
|
439
330
|
}
|
|
@@ -535,28 +426,12 @@ export class VirtualKeyboard {
|
|
|
535
426
|
case "←":
|
|
536
427
|
const leftNewPos = Math.max(0, start - 1);
|
|
537
428
|
this.currentInput.setSelectionRange(leftNewPos, leftNewPos);
|
|
538
|
-
// Force immediate focus and ensure cursor is visible
|
|
539
|
-
this.currentInput.focus();
|
|
540
|
-
setTimeout(() => {
|
|
541
|
-
this.currentInput.setSelectionRange(leftNewPos, leftNewPos);
|
|
542
|
-
this.currentInput.focus();
|
|
543
|
-
}, 5);
|
|
544
|
-
// Skip the general cursor repositioning for arrow keys
|
|
545
|
-
this.isVirtualKeyboardActive = false;
|
|
546
429
|
break;
|
|
547
430
|
|
|
548
431
|
case "→":
|
|
549
432
|
const bufferLength = this.getCurrentBuffer().length;
|
|
550
433
|
const rightNewPos = Math.min(bufferLength, start + 1);
|
|
551
434
|
this.currentInput.setSelectionRange(rightNewPos, rightNewPos);
|
|
552
|
-
// Force immediate focus and ensure cursor is visible
|
|
553
|
-
this.currentInput.focus();
|
|
554
|
-
setTimeout(() => {
|
|
555
|
-
this.currentInput.setSelectionRange(rightNewPos, rightNewPos);
|
|
556
|
-
this.currentInput.focus();
|
|
557
|
-
}, 5);
|
|
558
|
-
// Skip the general cursor repositioning for arrow keys
|
|
559
|
-
this.isVirtualKeyboardActive = false;
|
|
560
435
|
break;
|
|
561
436
|
|
|
562
437
|
case "↑":
|
|
@@ -602,24 +477,20 @@ export class VirtualKeyboard {
|
|
|
602
477
|
|
|
603
478
|
if (isShiftActive && !isCapsActive) this.toggleShift();
|
|
604
479
|
|
|
605
|
-
// Only apply general focus and cursor management if not an arrow key
|
|
606
|
-
// Arrow keys handle their own cursor positioning and return early
|
|
607
480
|
this.currentInput.focus();
|
|
608
|
-
|
|
609
|
-
// Use setTimeout to ensure cursor positioning happens after DOM updates
|
|
481
|
+
|
|
610
482
|
setTimeout(() => {
|
|
611
483
|
this.currentInput.focus();
|
|
612
|
-
// Ensure cursor is visible by setting selection range
|
|
613
484
|
const cursorPos = this.currentInput.selectionStart;
|
|
614
485
|
this.currentInput.setSelectionRange(cursorPos, cursorPos);
|
|
615
486
|
}, 0);
|
|
616
487
|
|
|
488
|
+
this.currentInput.focus();
|
|
617
489
|
const event = new Event("input", { bubbles: true });
|
|
618
490
|
this.currentInput.dispatchEvent(event);
|
|
619
491
|
|
|
620
|
-
// Reset flag after virtual keyboard operation is complete
|
|
492
|
+
// Reset the flag after virtual keyboard operation is complete
|
|
621
493
|
this.isVirtualKeyboardActive = false;
|
|
622
|
-
// console.log(keyPressed)
|
|
623
494
|
}
|
|
624
495
|
|
|
625
496
|
// [3]
|
|
@@ -632,17 +503,11 @@ export class VirtualKeyboard {
|
|
|
632
503
|
const newBuffer = buffer.slice(0, start) + textvalue + buffer.slice(end);
|
|
633
504
|
this.setCurrentBuffer(newBuffer);
|
|
634
505
|
|
|
635
|
-
// Calculate new cursor position first
|
|
636
506
|
const newCursorPos = start + textvalue.length;
|
|
637
507
|
|
|
638
|
-
// Update displayed value without preserving old cursor position
|
|
639
508
|
this.updateDisplayedValue(false);
|
|
640
509
|
|
|
641
|
-
// Set cursor position after the inserted text
|
|
642
510
|
this.currentInput.setSelectionRange(newCursorPos, newCursorPos);
|
|
643
|
-
|
|
644
|
-
// Ensure the input remains focused with visible cursor
|
|
645
|
-
// this.currentInput.focus();
|
|
646
511
|
}
|
|
647
512
|
|
|
648
513
|
// Helper: get current plaintext buffer for active input
|
|
@@ -677,22 +542,110 @@ export class VirtualKeyboard {
|
|
|
677
542
|
}
|
|
678
543
|
}
|
|
679
544
|
|
|
680
|
-
// Handle real keyboard input
|
|
681
|
-
|
|
545
|
+
// Handle real keyboard keydown events to track actual input before masking
|
|
546
|
+
handleRealKeyboardKeydown(input, event) {
|
|
682
547
|
if (!input) return;
|
|
683
548
|
|
|
684
|
-
|
|
685
|
-
const
|
|
549
|
+
const key = event.key;
|
|
550
|
+
const start = input.selectionStart;
|
|
551
|
+
const end = input.selectionEnd;
|
|
552
|
+
const buffer = this.getCurrentBuffer();
|
|
686
553
|
|
|
687
|
-
//
|
|
688
|
-
|
|
554
|
+
// Skip modifier keys and special keys
|
|
555
|
+
if (event.ctrlKey || event.altKey || event.metaKey ||
|
|
556
|
+
['Shift', 'Control', 'Alt', 'Meta', 'CapsLock', 'Tab', 'Escape',
|
|
557
|
+
'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End',
|
|
558
|
+
'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12'].includes(key)) {
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
689
561
|
|
|
690
|
-
|
|
691
|
-
this.updateDomEncrypted(input, currentValue);
|
|
562
|
+
let newBuffer = buffer;
|
|
692
563
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
564
|
+
if (key === 'Backspace') {
|
|
565
|
+
if (start === end && start > 0) {
|
|
566
|
+
// Single character deletion
|
|
567
|
+
newBuffer = buffer.slice(0, start - 1) + buffer.slice(start);
|
|
568
|
+
} else if (start !== end) {
|
|
569
|
+
// Range deletion
|
|
570
|
+
newBuffer = buffer.slice(0, start) + buffer.slice(end);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// Prevent default and update manually
|
|
574
|
+
event.preventDefault();
|
|
575
|
+
this.setCurrentBuffer(newBuffer);
|
|
576
|
+
this.updateDisplayedValue(false);
|
|
577
|
+
|
|
578
|
+
// Set cursor position
|
|
579
|
+
const newCursorPos = start === end ? Math.max(0, start - 1) : start;
|
|
580
|
+
setTimeout(() => {
|
|
581
|
+
input.setSelectionRange(newCursorPos, newCursorPos);
|
|
582
|
+
input.focus();
|
|
583
|
+
}, 0);
|
|
584
|
+
|
|
585
|
+
} else if (key === 'Delete') {
|
|
586
|
+
if (start === end && start < buffer.length) {
|
|
587
|
+
// Single character deletion forward
|
|
588
|
+
newBuffer = buffer.slice(0, start) + buffer.slice(start + 1);
|
|
589
|
+
} else if (start !== end) {
|
|
590
|
+
// Range deletion
|
|
591
|
+
newBuffer = buffer.slice(0, start) + buffer.slice(end);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Prevent default and update manually
|
|
595
|
+
event.preventDefault();
|
|
596
|
+
this.setCurrentBuffer(newBuffer);
|
|
597
|
+
this.updateDisplayedValue(false);
|
|
598
|
+
|
|
599
|
+
// Set cursor position
|
|
600
|
+
setTimeout(() => {
|
|
601
|
+
input.setSelectionRange(start, start);
|
|
602
|
+
input.focus();
|
|
603
|
+
}, 0);
|
|
604
|
+
|
|
605
|
+
} else if (key === 'Enter') {
|
|
606
|
+
if (input.tagName === 'TEXTAREA') {
|
|
607
|
+
newBuffer = buffer.slice(0, start) + '\n' + buffer.slice(end);
|
|
608
|
+
|
|
609
|
+
// Prevent default and update manually
|
|
610
|
+
event.preventDefault();
|
|
611
|
+
this.setCurrentBuffer(newBuffer);
|
|
612
|
+
this.updateDisplayedValue(false);
|
|
613
|
+
|
|
614
|
+
// Set cursor position
|
|
615
|
+
const newCursorPos = start + 1;
|
|
616
|
+
setTimeout(() => {
|
|
617
|
+
input.setSelectionRange(newCursorPos, newCursorPos);
|
|
618
|
+
input.focus();
|
|
619
|
+
}, 0);
|
|
620
|
+
}
|
|
621
|
+
// For input fields, let the default behavior handle form submission
|
|
622
|
+
|
|
623
|
+
} else if (key.length === 1) {
|
|
624
|
+
// Regular character input
|
|
625
|
+
newBuffer = buffer.slice(0, start) + key + buffer.slice(end);
|
|
626
|
+
|
|
627
|
+
// Prevent default and update manually
|
|
628
|
+
event.preventDefault();
|
|
629
|
+
this.setCurrentBuffer(newBuffer);
|
|
630
|
+
this.updateDisplayedValue(false);
|
|
631
|
+
|
|
632
|
+
// Set cursor position
|
|
633
|
+
const newCursorPos = start + key.length;
|
|
634
|
+
setTimeout(() => {
|
|
635
|
+
input.setSelectionRange(newCursorPos, newCursorPos);
|
|
636
|
+
input.focus();
|
|
637
|
+
}, 0);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// Handle real keyboard input and encrypt it (simplified version)
|
|
642
|
+
handleRealKeyboardInput(input, event) {
|
|
643
|
+
if (!input) return;
|
|
644
|
+
|
|
645
|
+
// For most cases, the keydown handler already took care of updating the buffer
|
|
646
|
+
// This method now just ensures the encryption is up to date
|
|
647
|
+
const currentBuffer = this.getCurrentBuffer();
|
|
648
|
+
this.updateDomEncrypted(input, currentBuffer);
|
|
696
649
|
}
|
|
697
650
|
|
|
698
651
|
// Helper: encrypt and store ciphertext in DOM attribute
|
|
@@ -855,7 +808,7 @@ export class VirtualKeyboard {
|
|
|
855
808
|
// [12]
|
|
856
809
|
scrambleEnglishKeys() {
|
|
857
810
|
const keys = document.querySelectorAll(
|
|
858
|
-
".key:not([data-key='scrambled']):not([data-key='std']):not([data-key='scr']):not([data-key='
|
|
811
|
+
".key:not([data-key='scrambled']):not([data-key='std']):not([data-key='scr']):not([data-key=' ']):not([data-key='Backspace']):not([data-key='Caps 🄰']):not([data-key='Shift ⇧']):not([data-key='Enter']):not([data-key='Tab ↹'])"
|
|
859
812
|
);
|
|
860
813
|
const englishAlphabet = "abcdefghijklmnopqrstuvwxyz/.,';\\][`1234567890-=".split("");
|
|
861
814
|
this.shuffleArray(englishAlphabet);
|
|
@@ -868,7 +821,7 @@ export class VirtualKeyboard {
|
|
|
868
821
|
// [13]
|
|
869
822
|
scrambleThaiKeys() {
|
|
870
823
|
const keys = document.querySelectorAll(
|
|
871
|
-
".key:not([data-key='scrambled']):not([data-key='scr']):not([data-key='Backspace']):not([data-key='Caps 🄰']):not([data-key='Shift ⇧']):not([data-key='Enter']):not([data-key='
|
|
824
|
+
".key:not([data-key='scrambled']):not([data-key='scr']):not([data-key='Backspace']):not([data-key='Caps 🄰']):not([data-key='Shift ⇧']):not([data-key='Enter']):not([data-key=' ']):not([data-key='Tab ↹'])"
|
|
872
825
|
);
|
|
873
826
|
const ThaiAlphabet = '_ๅ/-ภถุึคตจขชๆไำพะัีรนยบลฃฟหกดเ้่าสวงผปแอิืทมใฝ%+๑๒๓๔ู฿๕๖๗๘๙๐"ฎฑธํ๊ณฯญฐ,ฅฤฆฏโฌ็๋ษศซ.ฦฬฒ?์ฺฮฉ)('.split("");
|
|
874
827
|
this.shuffleArray(ThaiAlphabet);
|
|
@@ -891,6 +844,21 @@ export class VirtualKeyboard {
|
|
|
891
844
|
});
|
|
892
845
|
}
|
|
893
846
|
|
|
847
|
+
|
|
848
|
+
scrambleFullLayoutKeys() {
|
|
849
|
+
const keys = document.querySelectorAll(
|
|
850
|
+
".key:not(.concat-keys):not([data-key='scrambled']):not([data-key='std']):not([data-key='scr']):not([data-key=' ']):not([data-key='Backspace']):not([data-key='Caps 🄰']):not([data-key='Shift ⇧']):not([data-key='Enter']):not([data-key='Tab ↹'])"
|
|
851
|
+
);
|
|
852
|
+
const englishChars = "abcdefghijklmnopqrstuvwxyz1234567890;'\\/][`,.-=".split("");
|
|
853
|
+
this.shuffleArray(englishChars);
|
|
854
|
+
keys.forEach((key, index) => {
|
|
855
|
+
if (index < englishChars.length) {
|
|
856
|
+
key.textContent = englishChars[index];
|
|
857
|
+
key.dataset.key = englishChars[index];
|
|
858
|
+
}
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
|
|
894
862
|
// [14] จัดการการลากคีย์บอร์ด
|
|
895
863
|
startDrag(event) {
|
|
896
864
|
this.isDragging = true;
|
package/layouts.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
export const layouts = {
|
|
2
2
|
full: [
|
|
3
|
-
// ["Esc", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "DEL⌦", "HOME", "END"],
|
|
3
|
+
// ["Esc", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "DEL⌦", "HOME", "END"],"ㅤ","ㅤ","ㅤ","ㅤ",
|
|
4
4
|
["`", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "Backspace"].concat(["+", "-", "*","/"]),
|
|
5
5
|
["Tab ↹", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "[", "]", "\\"].concat(["7", "8", "9", "%"]),
|
|
6
6
|
["Caps 🄰", "a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "'", "Enter"].concat(["4", "5", "6", "_"]),
|
|
7
7
|
["Shift ⇧", "z", "x", "c", "v", "b", "n", "m", ",", ".", "/", "Shift ⇧", "↑"].concat(["1", "2", "3", "="]),
|
|
8
|
-
["
|
|
8
|
+
["scrambled", " ", "←", "↓", "→"].concat(["0", "."]),
|
|
9
9
|
],
|
|
10
10
|
en: [
|
|
11
11
|
["`", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "Backspace"],
|
package/package.json
CHANGED
package/style/kbstyle.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// CSS-in-JS version of kbstyle.css
|
|
2
2
|
export const css = `
|
|
3
|
-
.keyboard-row,.virtual-keyboard{display:flex;display:flex}.keyb,.keyboard-key{cursor:pointer;font-size:16px;text-align:center}.virtual-keyboard{flex-direction:column;width:100%;max-width:800px;margin:0 auto;border:2px solid #333;border-radius:10px;padding:20px;background-color:#f5f5f5;box-shadow:0 4px 10px rgba(0,0,0,.1)}.keyboard-row{justify-content:space-between}.keyboard-key{flex:1;padding:10px 15px;margin:2px;border:1px solid #ccc;border-radius:5px;background-color:#fff;transition:background-color .2s}.keyboard-active,input:focus,textarea:focus{border-color:#007bff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.backspacew{flex:1.5;width:260px}.keyboard-key:hover{background:#e0e0e0}.keyboard-key:active{background:#d0d0d0;transform:translateY(1px)}.keyboard-row{display:flex;justify-content:flex-start;margin:5px 0}.controls{margin-bottom:20px}button,select{margin-right:10px;padding:8px 12px;border:1px solid #ccc;border-radius:4px;font-size:14px;cursor:pointer}button:hover{background:#f0f0f0}input:focus,textarea:focus{outline:0}#keyboard{position:absolute;cursor:move;top:80%;left:50%;transform:translate(-50%,-50%)}.keyb{color:#fff;border:#000;width:40px;height:25px;line-height:50px;margin:8px}.keyboard-row .active{background:#c7c7c7}body.dark-mode .virtual-keyboard{background-color:#333;box-shadow:0 4px 8px rgba(247,245,245,.1)}body.dark-mode{background-color:#333}body.dark-mode .login-container{background-color:#000;box-shadow:0 4px 8px rgba(247,245,245,.1);color:#fff}body.dark-mode .login-container input{background-color:#3333335d;color:#fff}body.dark-mode .login-container button{background-color:#005703;color:#fff}.fa-keyboard{color:#000}body.dark-mode .keyb .fa-keyboard{color:#ffffffde}body.dark-mode .keyboard-key.key{background-color:#222;color:#ffffffde}[data-key="Caps 🄰"],[data-key="Shift ⇧"],[data-key="Tab ↹"]{min-width:90px}.keyboard-key.key[data-key="Shift ⇧"]{color:#333}.virtual-keyboard.full [data-key=Enter]{min-width:95px}.virtual-keyboard.full [data-key="Shift ⇧"]{min-width:92px}.virtual-keyboard.full [data-key=" "]{min-width:
|
|
3
|
+
.keyboard-row,.virtual-keyboard{display:flex;display:flex}.keyb,.keyboard-key{cursor:pointer;font-size:16px;text-align:center}.virtual-keyboard{flex-direction:column;width:100%;max-width:800px;margin:0 auto;border:2px solid #333;border-radius:10px;padding:20px;background-color:#f5f5f5;box-shadow:0 4px 10px rgba(0,0,0,.1)}.keyboard-row{justify-content:space-between}.keyboard-key{flex:1;padding:10px 15px;margin:2px;border:1px solid #ccc;border-radius:5px;background-color:#fff;transition:background-color .2s}.keyboard-active,input:focus,textarea:focus{border-color:#007bff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.backspacew{flex:1.5;width:260px}.keyboard-key:hover{background:#e0e0e0}.keyboard-key:active{background:#d0d0d0;transform:translateY(1px)}.keyboard-row{display:flex;justify-content:flex-start;margin:5px 0}.controls{margin-bottom:20px}button,select{margin-right:10px;padding:8px 12px;border:1px solid #ccc;border-radius:4px;font-size:14px;cursor:pointer}button:hover{background:#f0f0f0}input:focus,textarea:focus{outline:0}#keyboard{position:absolute;cursor:move;top:80%;left:50%;transform:translate(-50%,-50%)}.keyb{color:#fff;border:#000;width:40px;height:25px;line-height:50px;margin:8px}.keyboard-row .active{background:#c7c7c7}body.dark-mode .virtual-keyboard{background-color:#333;box-shadow:0 4px 8px rgba(247,245,245,.1)}body.dark-mode{background-color:#333}body.dark-mode .login-container{background-color:#000;box-shadow:0 4px 8px rgba(247,245,245,.1);color:#fff}body.dark-mode .login-container input{background-color:#3333335d;color:#fff}body.dark-mode .login-container button{background-color:#005703;color:#fff}.fa-keyboard{color:#000}body.dark-mode .keyb .fa-keyboard{color:#ffffffde}body.dark-mode .keyboard-key.key{background-color:#222;color:#ffffffde}[data-key="Caps 🄰"],[data-key="Shift ⇧"],[data-key="Tab ↹"]{min-width:90px}.keyboard-key.key[data-key="Shift ⇧"]{color:#333}.virtual-keyboard.full [data-key=Enter]{min-width:95px}.virtual-keyboard.full [data-key="Shift ⇧"]{min-width:92px}.virtual-keyboard.full [data-key=" "]{min-width:563.8px}.virtual-keyboard.full .concat-keys[data-key="0"]{min-width:102px}.virtual-keyboard.full{min-width:1000px}.virtual-keyboard.Scnum,.virtual-keyboard.numpad{max-width:300px}.hidden{display:none!important}[data-key="Tab ↹"],[data-key=Backspace]{min-width:80px}[data-key="Shift ⇧"]{min-width:100px}[data-key=" "]{min-width:670px}
|
|
4
4
|
`;
|
|
5
5
|
|
|
6
6
|
export function injectCSS() {
|