/* rondPrompt:替代 window.prompt 的轻量输入弹层。 * * 为什么必须换掉 prompt():它会把提示文本与默认值写进地址栏 * (Chrome 拼成 `当前页?新的活动名称=吃饭` 这类 URL 并留进历史), * 刷新即丢、分享出去还带着原文。confirm / alert 不污染地址栏,只有 prompt 有这毛病。 * * 能力对齐原生 prompt:返回 Promise,回车确认、Esc 取消、点遮罩取消。 * 额外做了两件 prompt 给不了的:只读模式下直接把内容放进一个可全选的框(复制链接用), * 以及读值时可要求「非空」(改名这种必填的场景直接少一层判空)。 * * 降级:浏览器不支持 时,弹层挂到页面顶部一条,同样不碰地址栏。 */ (function (global) { 'use strict'; function supportsDialog() { var d = document.createElement('dialog'); // 无头 / 老浏览器里 showModal 可能不存在,只看 typeof return typeof d.showModal === 'function'; } function buildFallback(title, value, opts) { var box = document.createElement('div'); box.className = 'prompt-fallback'; var lab = document.createElement('label'); lab.textContent = title; lab.style.fontSize = '13px'; var input = document.createElement('input'); input.type = 'text'; input.value = value || ''; var ok = document.createElement('button'); ok.type = 'button'; ok.className = 'btn primary'; ok.textContent = '确定'; var cancel = document.createElement('button'); cancel.type = 'button'; cancel.className = 'btn'; cancel.textContent = '取消'; var done = false; function finish(v) { if (done) return; done = true; document.removeEventListener('keydown', onKey, true); box.remove(); resolve(v); } function onKey(e) { if (e.key === 'Escape') { e.preventDefault(); finish(null); } } ok.addEventListener('click', function () { finish(input.value); }); cancel.addEventListener('click', function () { finish(null); }); document.addEventListener('keydown', onKey, true); lab.appendChild(document.createElement('br')); lab.appendChild(input); var acts = document.createElement('div'); acts.style.cssText = 'display:flex;gap:8px'; acts.appendChild(cancel); acts.appendChild(ok); lab.appendChild(acts); box.appendChild(lab); document.body.appendChild(box); input.focus(); input.select(); var resolve; var p = new Promise(function (r) { resolve = r; }); return p; } /** * 弹一个输入框。 * @param {string} title 标题(原文) * @param {string} [value] 初始值 * @param {object} [opts] { readonly: 只读展示(复制链接), required: 取消时忽略空值 } * @returns {Promise} */ function ask(title, value, opts) { opts = opts || {}; if (!supportsDialog()) return buildFallback(title, value, opts); return new Promise(function (resolve) { var dlg = document.createElement('dialog'); dlg.className = 'prompt-box'; var body = document.createElement('div'); body.className = 'pb-body'; var h = document.createElement('div'); h.className = 'pb-title'; h.textContent = title; var input = document.createElement('input'); input.className = 'pb-input'; input.type = 'text'; input.value = value == null ? '' : String(value); if (opts.readonly) input.readOnly = true; body.appendChild(h); body.appendChild(input); var acts = document.createElement('div'); acts.className = 'pb-acts'; var cancel = document.createElement('button'); cancel.type = 'button'; cancel.className = 'btn'; cancel.textContent = '取消'; var ok = document.createElement('button'); ok.type = 'button'; ok.className = 'btn primary'; ok.textContent = '确定'; acts.appendChild(cancel); acts.appendChild(ok); dlg.appendChild(body); dlg.appendChild(acts); var settled = false; function finish(v) { if (settled) return; settled = true; dlg.remove(); resolve(v); } function submit() { var v = input.value; // required:空值视作取消(改名这种必填的场景,少一层判空) if (opts.required && !v.trim()) return; finish(opts.readonly ? v : v); } cancel.addEventListener('click', function () { finish(null); }); ok.addEventListener('click', submit); input.addEventListener('keydown', function (e) { if (e.key === 'Enter') { e.preventDefault(); submit(); } }); // 点遮罩(dialog 元素本身)= 取消。inner 区域点击要挡住,否则点标题也算取消。 dlg.addEventListener('click', function (e) { if (e.target === dlg) finish(null); }); // Esc 触发 cancel 事件( 原生行为),统一在这收口 dlg.addEventListener('cancel', function (e) { e.preventDefault(); finish(null); }); document.body.appendChild(dlg); dlg.showModal(); input.focus(); // 只读场景(复制链接)全选好让用户直接 Ctrl+C;可编辑场景选中原值便于改写 input.select(); }); } global.rondPrompt = { ask: ask }; })(window);