251 lines
10 KiB
JavaScript
251 lines
10 KiB
JavaScript
(function (global) {
|
|
'use strict';
|
|
|
|
var DISPLAY_NAME_KEY = 'roomLobbyDisplayName';
|
|
var PLAYER_NAME_KEY = 'playerName';
|
|
var MAX_LEN = 24;
|
|
|
|
function sanitizeDisplayName(raw) {
|
|
var s = String(raw == null ? '' : raw).trim();
|
|
if (!s) return '';
|
|
try {
|
|
s = s.normalize('NFKC');
|
|
} catch (e) { /* ignore */ }
|
|
s = s.replace(/[\x00-\x1f\x7f]/g, '');
|
|
if (s.length > MAX_LEN) s = s.slice(0, MAX_LEN);
|
|
return s;
|
|
}
|
|
|
|
function readDisplayName() {
|
|
try {
|
|
return (
|
|
localStorage.getItem(DISPLAY_NAME_KEY) ||
|
|
localStorage.getItem(PLAYER_NAME_KEY) ||
|
|
'MONE'
|
|
).trim() || 'MONE';
|
|
} catch (e) {
|
|
return 'MONE';
|
|
}
|
|
}
|
|
|
|
function syncDisplayNameDom(displayName) {
|
|
var text = String(displayName || '').trim();
|
|
if (!text) return;
|
|
var upper = text.toUpperCase();
|
|
var ids = [
|
|
'lobby-profile-name',
|
|
'room-lobby-profile-overlay-name',
|
|
'room-lobby-profile-name'
|
|
];
|
|
ids.forEach(function (id) {
|
|
var el = document.getElementById(id);
|
|
if (el) el.textContent = upper;
|
|
});
|
|
try {
|
|
document.querySelectorAll('[data-jd-display-name]').forEach(function (el) {
|
|
el.textContent = upper;
|
|
});
|
|
} catch (e2) { /* ignore */ }
|
|
}
|
|
|
|
function replaceNickQueryParam(displayName) {
|
|
try {
|
|
var u = new URL(global.location.href);
|
|
if (!u.searchParams.has('nick')) return;
|
|
u.searchParams.set('nick', displayName);
|
|
global.history.replaceState({}, '', u.pathname + u.search + u.hash);
|
|
} catch (e) { /* ignore */ }
|
|
}
|
|
|
|
/**
|
|
* @param {string} nextName
|
|
* @param {{ socket?: object, silent?: boolean }} [options]
|
|
* @returns {{ ok: boolean, displayName?: string, error?: string }}
|
|
*/
|
|
function applyDisplayNameChange(nextName, options) {
|
|
var safe = sanitizeDisplayName(nextName);
|
|
if (!safe) return { ok: false, error: 'empty' };
|
|
var opts = options || {};
|
|
try {
|
|
localStorage.setItem(DISPLAY_NAME_KEY, safe);
|
|
localStorage.setItem(PLAYER_NAME_KEY, safe);
|
|
} catch (e) { /* ignore */ }
|
|
syncDisplayNameDom(safe);
|
|
replaceNickQueryParam(safe);
|
|
try {
|
|
global.dispatchEvent(new CustomEvent('jd-display-name-changed', {
|
|
detail: { displayName: safe }
|
|
}));
|
|
} catch (e2) { /* ignore */ }
|
|
var sock = opts.socket || global.__jdGameSocket || null;
|
|
if (sock && typeof sock.emit === 'function' && sock.connected) {
|
|
sock.emit('update-display-name', { nickname: safe }, function (res) {
|
|
if (res && res.ok && res.nickname) {
|
|
try {
|
|
localStorage.setItem(DISPLAY_NAME_KEY, res.nickname);
|
|
localStorage.setItem(PLAYER_NAME_KEY, res.nickname);
|
|
} catch (e3) { /* ignore */ }
|
|
syncDisplayNameDom(res.nickname);
|
|
}
|
|
});
|
|
}
|
|
return { ok: true, displayName: safe };
|
|
}
|
|
|
|
var editorReady = false;
|
|
|
|
function ensureNameEditorDom() {
|
|
if (editorReady && document.getElementById('jd-display-name-editor-overlay')) return;
|
|
if (!document.getElementById('jd-display-name-editor-style')) {
|
|
var st = document.createElement('style');
|
|
st.id = 'jd-display-name-editor-style';
|
|
st.textContent = '' +
|
|
'.jd-dne-overlay{position:fixed;inset:0;z-index:10080;display:flex;align-items:center;justify-content:center;padding:16px;}' +
|
|
'.jd-dne-overlay.is-hidden{display:none!important;}' +
|
|
'.jd-dne-backdrop{position:absolute;inset:0;background:rgba(0,0,0,.72);}' +
|
|
'.jd-dne-dialog{position:relative;z-index:1;width:min(420px,calc(100vw - 32px));padding:22px 20px 18px;border-radius:14px;' +
|
|
'background:linear-gradient(180deg,#0d2848 0%,#061528 100%);border:2px solid rgba(56,232,255,.85);box-shadow:0 0 28px rgba(56,232,255,.35);color:#eaf8ff;font-family:Kanit,Segoe UI,sans-serif;}' +
|
|
'.jd-dne-title{margin:0 0 14px;font-size:1.25rem;font-weight:700;text-align:center;letter-spacing:.02em;}' +
|
|
'.jd-dne-input{display:block;width:100%;box-sizing:border-box;padding:12px 14px;border-radius:10px;border:2px solid rgba(56,232,255,.55);' +
|
|
'background:rgba(0,20,40,.85);color:#fff;font-size:1.05rem;font-family:inherit;outline:none;}' +
|
|
'.jd-dne-input:focus{border-color:#5ce9ff;box-shadow:0 0 0 3px rgba(92,233,255,.25);}' +
|
|
'.jd-dne-hint{margin:8px 0 0;font-size:.82rem;opacity:.75;text-align:center;}' +
|
|
'.jd-dne-error{margin:8px 0 0;font-size:.85rem;color:#ff8a8a;text-align:center;min-height:1.1em;}' +
|
|
'.jd-dne-actions{display:flex;gap:10px;margin-top:18px;justify-content:center;}' +
|
|
'.jd-dne-btn{min-width:110px;padding:10px 16px;border-radius:10px;border:2px solid transparent;font-size:1rem;font-weight:600;font-family:inherit;cursor:pointer;}' +
|
|
'.jd-dne-btn--cancel{background:rgba(255,255,255,.08);border-color:rgba(255,255,255,.25);color:#e2e8f0;}' +
|
|
'.jd-dne-btn--confirm{background:linear-gradient(180deg,#2ee6ff,#0ea5c9);border-color:#7df4ff;color:#042033;}';
|
|
document.head.appendChild(st);
|
|
}
|
|
if (!document.getElementById('jd-display-name-editor-overlay')) {
|
|
var wrap = document.createElement('div');
|
|
wrap.id = 'jd-display-name-editor-overlay';
|
|
wrap.className = 'jd-dne-overlay is-hidden';
|
|
wrap.setAttribute('aria-hidden', 'true');
|
|
wrap.innerHTML = '' +
|
|
'<div class="jd-dne-backdrop" id="jd-dne-backdrop" aria-hidden="true"></div>' +
|
|
'<div class="jd-dne-dialog" role="dialog" aria-modal="true" aria-labelledby="jd-dne-title">' +
|
|
' <h2 class="jd-dne-title" id="jd-dne-title">แก้ไขชื่อผู้เล่น</h2>' +
|
|
' <input type="text" id="jd-dne-input" class="jd-dne-input" maxlength="24" autocomplete="nickname" aria-label="ชื่อผู้เล่น">' +
|
|
' <p class="jd-dne-hint">สูงสุด 24 ตัวอักษร — ชื่อนี้จะแสดงในเกม</p>' +
|
|
' <p class="jd-dne-error" id="jd-dne-error" role="alert"></p>' +
|
|
' <div class="jd-dne-actions">' +
|
|
' <button type="button" class="jd-dne-btn jd-dne-btn--cancel" id="jd-dne-cancel">ยกเลิก</button>' +
|
|
' <button type="button" class="jd-dne-btn jd-dne-btn--confirm" id="jd-dne-confirm">ยืนยัน</button>' +
|
|
' </div>' +
|
|
'</div>';
|
|
document.body.appendChild(wrap);
|
|
}
|
|
editorReady = true;
|
|
}
|
|
|
|
/**
|
|
* เปิดกล่องแก้ชื่อ (ใช้แทน window.prompt — ทำงานใน Cursor browser / mobile)
|
|
* @param {{ initialValue?: string, title?: string }} [options]
|
|
* @returns {Promise<string|null>} ชื่อใหม่ หรือ null ถ้ายกเลิก
|
|
*/
|
|
function openDisplayNameEditor(options) {
|
|
ensureNameEditorDom();
|
|
var opts = options || {};
|
|
var overlay = document.getElementById('jd-display-name-editor-overlay');
|
|
var input = document.getElementById('jd-dne-input');
|
|
var errEl = document.getElementById('jd-dne-error');
|
|
var btnOk = document.getElementById('jd-dne-confirm');
|
|
var btnCancel = document.getElementById('jd-dne-cancel');
|
|
var backdrop = document.getElementById('jd-dne-backdrop');
|
|
var titleEl = document.getElementById('jd-dne-title');
|
|
if (!overlay || !input || !btnOk || !btnCancel) {
|
|
return Promise.resolve(null);
|
|
}
|
|
|
|
return new Promise(function (resolve) {
|
|
var settled = false;
|
|
function finish(value) {
|
|
if (settled) return;
|
|
settled = true;
|
|
overlay.classList.add('is-hidden');
|
|
overlay.setAttribute('aria-hidden', 'true');
|
|
document.removeEventListener('keydown', onDocKey, true);
|
|
resolve(value);
|
|
}
|
|
|
|
function onDocKey(e) {
|
|
if (overlay.classList.contains('is-hidden')) return;
|
|
if (e.key === 'Escape') {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
finish(null);
|
|
}
|
|
}
|
|
|
|
function submit() {
|
|
if (errEl) errEl.textContent = '';
|
|
var safe = sanitizeDisplayName(input.value);
|
|
if (!safe) {
|
|
if (errEl) errEl.textContent = 'กรุณากรอกชื่อผู้เล่น';
|
|
input.focus();
|
|
return;
|
|
}
|
|
finish(safe);
|
|
}
|
|
|
|
if (titleEl && opts.title) titleEl.textContent = opts.title;
|
|
input.value = opts.initialValue != null ? String(opts.initialValue) : readDisplayName();
|
|
if (errEl) errEl.textContent = '';
|
|
overlay.classList.remove('is-hidden');
|
|
overlay.setAttribute('aria-hidden', 'false');
|
|
document.addEventListener('keydown', onDocKey, true);
|
|
|
|
btnOk.onclick = function (e) {
|
|
e.preventDefault();
|
|
submit();
|
|
};
|
|
btnCancel.onclick = function (e) {
|
|
e.preventDefault();
|
|
finish(null);
|
|
};
|
|
backdrop.onclick = function () {
|
|
finish(null);
|
|
};
|
|
input.onkeydown = function (e) {
|
|
if (e.key === 'Enter') {
|
|
e.preventDefault();
|
|
submit();
|
|
}
|
|
};
|
|
|
|
requestAnimationFrame(function () {
|
|
input.focus();
|
|
input.select();
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* เปิด editor แล้ว apply ชื่อ (socket sync ถ้ามี)
|
|
* @param {{ socket?: object }} [options]
|
|
* @returns {Promise<{ ok: boolean, displayName?: string, cancelled?: boolean }>}
|
|
*/
|
|
function promptAndApplyDisplayName(options) {
|
|
var opts = options || {};
|
|
return openDisplayNameEditor({ initialValue: readDisplayName() }).then(function (draft) {
|
|
if (draft == null) return { ok: false, cancelled: true };
|
|
return applyDisplayNameChange(draft, opts);
|
|
});
|
|
}
|
|
|
|
global.jdDisplayName = {
|
|
key: DISPLAY_NAME_KEY,
|
|
read: readDisplayName,
|
|
sanitize: sanitizeDisplayName,
|
|
syncDom: syncDisplayNameDom,
|
|
apply: applyDisplayNameChange,
|
|
openEditor: openDisplayNameEditor,
|
|
promptAndApply: promptAndApplyDisplayName
|
|
};
|
|
global.jdApplyDisplayNameChange = applyDisplayNameChange;
|
|
global.jdReadDisplayName = readDisplayName;
|
|
global.jdOpenDisplayNameEditor = openDisplayNameEditor;
|
|
global.jdPromptAndApplyDisplayName = promptAndApplyDisplayName;
|
|
})(typeof window !== 'undefined' ? window : this);
|