Auto-submit four digit launcher PIN

This commit is contained in:
seo
2026-06-17 19:52:11 +09:00
parent 5275aa17c2
commit db48512585
3 changed files with 64 additions and 38 deletions
+59 -31
View File
@@ -2,6 +2,7 @@ const shortcut = document.getElementById('findMyShortcut');
let failIndex = 0;
const apiUrl = '/custom/launcher/api.php';
const PIN_LENGTH = 4;
const iconItems = [
{
@@ -98,14 +99,12 @@ function showPasswordForm(onSuccess) {
const title = createFormHeader('비밀번호', renderIcons);
const input = createPinInput('blog_secret', '비밀번호');
const button = createButton('확인');
const msg = document.createElement('div');
msg.id = 'failMsg';
const inputRow = document.createElement('div');
inputRow.className = 'launcher-input-row';
inputRow.appendChild(input);
inputRow.appendChild(button);
const column = document.createElement('div');
column.className = 'launcher-form-column';
@@ -116,6 +115,7 @@ function showPasswordForm(onSuccess) {
const submit = async () => {
const password = onlyDigits(input.value);
input.value = password;
if (password.length !== PIN_LENGTH) return;
try {
const messageRes = await fetch(`${apiUrl}?failMessages=1`);
@@ -152,7 +152,6 @@ function showPasswordForm(onSuccess) {
const keypad = createPinKeypad([input], submit);
column.appendChild(keypad);
button.addEventListener('click', submit);
attachPinInputEvents(input, submit);
input.focus();
}
@@ -165,17 +164,20 @@ function showPasswordChangeForm() {
const currentInput = createPinInput('current_password', '현재 암호');
const newInput = createPinInput('new_password', '새 암호');
const confirmInput = createPinInput('confirm_password', '새 암호 확인');
const button = createButton('변경');
const msg = document.createElement('div');
msg.id = 'failMsg';
const status = document.createElement('div');
status.className = 'launcher-pin-status';
status.textContent = '현재 암호 입력';
const column = document.createElement('div');
column.className = 'launcher-form-column';
column.appendChild(title);
column.appendChild(status);
column.appendChild(currentInput);
column.appendChild(newInput);
column.appendChild(confirmInput);
column.appendChild(button);
column.appendChild(msg);
shortcut.appendChild(column);
@@ -187,8 +189,13 @@ function showPasswordChangeForm() {
newInput.value = newPassword;
confirmInput.value = confirmPassword;
if ([currentPassword, newPassword, confirmPassword].some(value => value.length !== PIN_LENGTH)) return;
if (newPassword !== confirmPassword) {
msg.textContent = '새 암호가 일치하지 않습니다.';
newInput.value = '';
confirmInput.value = '';
newInput.focus();
return;
}
if (!isDigitPin(currentPassword) || !isDigitPin(newPassword)) {
@@ -213,22 +220,34 @@ function showPasswordChangeForm() {
}
msg.style.color = 'red';
msg.textContent = data.message === 'password_too_short'
? '새 암호는 4자리 이상이어야 합니다.'
: (data.message === 'password_digits_only' ? '암호는 숫자만 입력할 수 있습니다.' : '현재 암호가 틀립니다.');
msg.textContent = data.message === 'password_digits_only' ? '암호는 숫자 4자리여야 합니다.' : '현재 암호가 틀립니다.';
if (data.message === 'current_password_invalid') {
currentInput.value = '';
currentInput.focus();
}
} catch (_) {
msg.style.color = 'red';
msg.textContent = '요청을 처리하지 못했습니다.';
}
};
const keypad = createPinKeypad([currentInput, newInput, confirmInput], submit);
const labels = new Map([
[currentInput, '현재 암호 입력'],
[newInput, '새 암호 입력'],
[confirmInput, '새 암호 확인 입력'],
]);
const syncStatus = () => {
const active = document.activeElement && labels.has(document.activeElement) ? document.activeElement : currentInput;
status.textContent = labels.get(active);
};
const keypad = createPinKeypad([currentInput, newInput, confirmInput], submit, syncStatus);
column.insertBefore(keypad, msg);
button.addEventListener('click', submit);
[currentInput, newInput, confirmInput].forEach(input => {
attachPinInputEvents(input, submit);
input.addEventListener('focus', syncStatus);
});
currentInput.focus();
syncStatus();
}
async function wakeComputer() {
@@ -365,7 +384,7 @@ function onlyDigits(value) {
}
function isDigitPin(value) {
return /^\d+$/.test(String(value || ''));
return new RegExp(`^\\d{${PIN_LENGTH}}$`).test(String(value || ''));
}
function createPinInput(id, placeholder) {
@@ -377,6 +396,7 @@ function createPinInput(id, placeholder) {
input.pattern = '[0-9]*';
input.autocomplete = 'off';
input.enterKeyHint = 'done';
input.maxLength = PIN_LENGTH;
input.readOnly = true;
input.className = 'launcher-pin-input';
input.setAttribute('aria-label', placeholder);
@@ -392,12 +412,14 @@ function attachPinInputEvents(input, onEnter) {
});
input.addEventListener('paste', e => {
e.preventDefault();
input.value += onlyDigits(e.clipboardData?.getData('text') || '');
input.value = (input.value + onlyDigits(e.clipboardData?.getData('text') || '')).slice(0, PIN_LENGTH);
if (input.value.length === PIN_LENGTH) onEnter();
});
input.addEventListener('keydown', e => {
if (/^\d$/.test(e.key)) {
e.preventDefault();
input.value += e.key;
input.value = (input.value + e.key).slice(0, PIN_LENGTH);
if (input.value.length === PIN_LENGTH) onEnter();
return;
}
if (e.key === 'Backspace') {
@@ -417,17 +439,24 @@ function attachPinInputEvents(input, onEnter) {
});
}
function createPinKeypad(inputs, onEnter) {
function createPinKeypad(inputs, onEnter, onActiveChange = null) {
let activeInput = inputs[0];
const keypad = document.createElement('div');
keypad.className = 'launcher-pin-keypad';
const setActiveInput = (input, clear = false) => {
activeInput = input;
if (clear) activeInput.value = '';
activeInput.focus();
document.querySelectorAll('.launcher-pin-input').forEach(el => el.classList.toggle('active', el === activeInput));
if (typeof onActiveChange === 'function') onActiveChange(activeInput);
};
inputs.forEach(input => {
input.addEventListener('focus', () => {
activeInput = input;
if (typeof onActiveChange === 'function') onActiveChange(activeInput);
});
input.addEventListener('click', () => {
activeInput = input;
input.focus();
setActiveInput(input, input.value.length > 0);
});
});
@@ -444,29 +473,28 @@ function createPinKeypad(inputs, onEnter) {
} else if (key === 'back') {
activeInput.value = activeInput.value.slice(0, -1);
} else {
activeInput.value += key;
activeInput.value = (activeInput.value + key).slice(0, PIN_LENGTH);
if (activeInput.value.length === PIN_LENGTH) {
if (inputs.length === 1) {
onEnter();
} else {
const idx = inputs.indexOf(activeInput);
if (idx >= 0 && idx < inputs.length - 1) {
const nextInput = inputs[idx + 1];
setActiveInput(nextInput, nextInput.value.length > 0);
} else {
onEnter();
}
}
}
}
});
keypad.appendChild(btn);
});
const done = document.createElement('button');
done.type = 'button';
done.className = 'launcher-pin-key launcher-pin-done';
done.textContent = '확인';
done.addEventListener('click', onEnter);
keypad.appendChild(done);
return keypad;
}
function createButton(text) {
const button = document.createElement('button');
button.type = 'button';
button.textContent = text;
return button;
}
function launcherAlert(name) {
if (typeof showAlert === 'function') {
showAlert(name);