Auto-submit four digit launcher PIN
This commit is contained in:
+59
-31
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user