js 代码片段模拟 window.prompt 函数

众所周知,思源不支持 prompt,确切的说 Electron 不支持。

但有时写脚本时确实需要一些交互式操作,如果使用思源 api 调用 Dialog 确实可以实现,但终究是麻烦了些。

就自己动手写了一个 prompt 模拟函数,放到 js 代码片段中即可,然后需要的时候就可以直接调用了。

效果

Snipaste20240816091141.png

Snipaste20240816091253.png

接口定义如下

// 弹出prompt对话框
// 参数说明
// message 提示消息,类似标题
// defaultValue 输入框默认值
// okName 确认按钮名字
// cancelName 取消按钮名字
// width对话框宽度,默认400,高度根据内容自适应
// modalMode,是否模态窗口,模态窗口,除了当前对话框,其他地方无法点击,false非模态窗,true模态窗
// clickOverlayClose 是否点击遮罩层关闭对话框,仅在模态窗口下有效
function showPrompt(message = '', defaultValue = '', okName="确定", cancelName="取消", width=400, modalMode=true, clickOverlayClose=false);


// 弹出自定义prompt对话框(可自定义定义复杂表单)
// 参数说明
// message 提示消息,类似标题
// html 对话框自定义表单内容的HTML代码
// onSubmit 提交时的回调函数,对话框的返回值依赖于这个回调函数的返回值
// onOpen 打开窗口时的回调函数,可以在打开窗口话进行一些初始化操作,比如某个输入框设为焦点
// okName 确认按钮名字
// cancelName 取消按钮名字
// width 对话框宽度,默认400px,高度根据内容自适应
// modalMode,是否模态窗口,模态窗口,除了当前对话框,其他地方无法点击,false非模态窗,true模态窗
// clickOverlayClose 是否点击遮罩层关闭对话框,仅在模态窗口下有效
function showPromptForm(message = '', html = '', onSubmit = null, onOpen = null, okName="确定", cancelName="取消", width=400, modalMode=true, clickOverlayClose=false);


调用示例

// showPrompt 示例用法
const result = await showPrompt('这里是提示文本', '');
console.log(result);


// showPromptForm 示例用法
const result = await showPromptForm('这里是提示文本', `
  <input type="text" id="promptName" placeholder="标题" />
  <input type="text" id="promptHref" placeholder="链接" />
`, (promptContainer) => {
    const inputs = promptContainer.querySelectorAll('input[type="text"]');
    const values = Array.from(inputs).map(input => input.value);
    return values;
}, ()=> {
    document.getElementById("promptName").focus();
});
console.log(result);

完整代码如下

// 功能:模拟window.prompt函数
// 使用示例
/*
// showPrompt 示例用法
const result = await showPrompt('这里是提示文本', '');
console.log(result);

// showPromptForm 示例用法
const result = await showPromptForm('这里是提示文本', `
  <input type="text" id="promptName" placeholder="标题" />
  <input type="text" id="promptHref" placeholder="链接" />
`, (promptContainer) => {
    const inputs = promptContainer.querySelectorAll('input[type="text"]');
    const values = Array.from(inputs).map(input => input.value);
    return values;
}, ()=> {
    document.getElementById("promptName").focus();
});
console.log(result);
*/
(()=>{
    // 样式变量
    const dialogStyleText = `
    .prompt-dialog {
        display: none;
        position: fixed;
        z-index: 9999;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        padding: 20px;
        border: 1px solid var(--b3-theme-surface-lighter);
        border-radius: var(--b3-border-radius-b);
        box-shadow: var(--b3-dialog-shadow);
        width: 400px; /* 设置初始宽度 */
        text-align: left; /* 左对齐 */
        font-size: 100%;
        font-family: var(--b3-font-family);
        color: var(--b3-theme-on-background);
        background-color: var(--b3-theme-surface);
    }
    .prompt-overlay {
        display: none;
        position: fixed;
        z-index: 9998;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        /*background-color: rgba(0, 0, 0, 0);*/
        background-color: var(--b3-mask-background);
        /*backdrop-filter: blur(3px);*/
    }
    .prompt-dialog .prompt-text {
        margin-bottom: 10px;
    }
    .prompt-dialog .prompt-input-container {
        margin-bottom: 20px;
    }
    .prompt-dialog .prompt-input-container input[type="text"] {
        width: calc(100% - 16px); /* 输入框占满容器宽度 */
        padding: 4px 8px;
        line-height: 20px;
        margin-bottom: 10px;
        color: var(--b3-theme-on-background);
        transition: box-shadow 120ms 0ms cubic-bezier(0, 0, 0.2, 1);
        background-color: var(--b3-theme-background);
        border: 0;
        border-radius: var(--b3-border-radius);
        box-shadow: inset 0 0 0 .6px var(--b3-theme-on-surface);
    }
    .prompt-dialog .prompt-input-container input[type="text"]:hover{
        box-shadow: inset 0 0 0 .6px var(--b3-theme-on-background);
    }
    .prompt-dialog .prompt-input-container input[type="text"]:focus{
        box-shadow: inset 0 0 0 1px var(--b3-theme-primary), 0 0 0 3px var(--b3-theme-primary-lightest);
    }
    .prompt-dialog .prompt-input-container input[type="text"]:last-child {
        margin-bottom: 0;
    }
    .prompt-dialog .prompt-button-container {
        display: flex;
        justify-content: flex-end; /* 按钮靠右 */
    }
    .prompt-dialog button {
        margin-left: 10px;
        line-height: 20px;
        padding: 4px 12px;
        color: var(--b3-theme-primary);
        box-shadow: inset 0 0 0 .6px var(--b3-theme-primary);
        background-color: rgba(0, 0, 0, 0);
        transition: box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1);
        border: 0;
        border-radius: var(--b3-border-radius);
    }
    .prompt-dialog button:focus, .prompt-dialog button:hover {
        background-color: var(--b3-theme-primary-lightest);
        box-shadow: inset 0 0 0 1px var(--b3-theme-primary);
        text-decoration: none;
    }
    @keyframes prompt-shake {
        0% { transform: translate(-50%, -50%) rotate(0deg); }
        25% { transform: translate(-50%, -50%) rotate(2deg); }
        50% { transform: translate(-50%, -50%) rotate(0deg); }
        75% { transform: translate(-50%, -50%) rotate(-2deg); }
        100% { transform: translate(-50%, -50%) rotate(0deg); }
    }
    .prompt-dialog.prompt-shake {
        animation: prompt-shake 0.5s ease-in-out;
    }
  `;
    // HTML模板
    const dialogHtml = `
    <div class="prompt-text" id="promptText">请输入</div>
    <div class="prompt-input-container">
      <input type="text" id="promptInput" />
    </div>
    <div class="prompt-button-container">
      <button id="promptCancel">取消</button>
      <button id="promptSubmit">确认</button>
    </div>
  `;
    // 创建样式
    const dialogStyle = document.createElement('style');
    dialogStyle.textContent = dialogStyleText;
    document.head.appendChild(dialogStyle);
    // 创建对话框
    const dialog = document.createElement('div');
    dialog.className = 'prompt-dialog';
    dialog.id = 'promptDialog';
    dialog.innerHTML = dialogHtml;
    document.body.appendChild(dialog);
    // 创建遮罩层
    const overlay = document.createElement('div');
    overlay.className = 'prompt-overlay';
    document.body.appendChild(overlay);
    // 获取元素
    const promptContainer = document.querySelector(".prompt-input-container");
    const submitButton = document.getElementById('promptSubmit');
    const cancelButton = document.getElementById('promptCancel');
    const promptText = document.getElementById('promptText');
    let focusEl;
    // 弹出对话框
    function showPrompt(message = '', defaultValue = '', okName="确定", cancelName="取消", width=400, modalMode=true, clickOverlayClose=false) {
        return new Promise((resolve, reject) => {
            // 初始化窗口
            dialog.style.width = (width || 400) + 'px';
            promptText.textContent = message;
            let input = document.getElementById('promptInput');
            if(!input) {
                promptContainer.innerHTML = `<input type="text" id="promptInput" />`;
                input = document.getElementById('promptInput');
            }
            input.value = defaultValue || '';
            submitButton.textContent = okName || '确定';
            cancelButton.textContent = cancelName || '取消';
            if(!cancelName) cancelButton.style.display = 'none';
            dialog.style.display = 'block';
            if(modalMode) overlay.style.display = 'block';
            input.focus();
            focusEl = promptContainer.querySelector(":focus");
            // 监听按钮点击事件
            submitButton.addEventListener('click', () => {
                dialog.style.display = 'none';
                if(modalMode) overlay.style.display = 'none';
                document.removeEventListener('keydown', handleKeydown);
                resolve(input.value);
            });
            cancelButton.addEventListener('click', () => {
                dialog.style.display = 'none';
                if(modalMode) overlay.style.display = 'none';
                document.removeEventListener('keydown', handleKeydown);
                resolve(null);
            });
            // 添加键盘事件监听器
            const handleKeydown = () => {
                if (event.key === 'Escape') {
                    cancelButton.click();
                } else if (event.key === 'Enter') {
                    submitButton.click();
                }
            };
            document.addEventListener('keydown', handleKeydown);
            // 监听焦点事件,focus冒泡必须第三参数是true
            promptContainer.addEventListener('focus', (event) => {
                focusEl = event.target;
            }, true);
            // 添加点击遮罩层关闭对话框的功能
            if(modalMode){
                overlay.addEventListener('click', () => {
                    // 点击遮罩层关闭弹窗
                    if(clickOverlayClose) cancelButton.click();
                    // 重新获取焦点
                    if(focusEl) focusEl.focus();
                    // 添加抖动类
                    dialog.classList.add('prompt-shake');
                    // 移除抖动类
                    setTimeout(() => {
                        dialog.classList.remove('prompt-shake');
                    }, 500);
                });
            }
        });
    }
    // 弹出对话框表单
    function showPromptForm(message = '', html = '', onSubmit = null, onOpen = null, okName="确定", cancelName="取消", width=400, modalMode=true, clickOverlayClose=false) {
        return new Promise((resolve, reject) => {
            // 初始化窗口
            dialog.style.width = (width || 400) + 'px';
            if(html) promptContainer.innerHTML = html;
            promptText.textContent = message;
            submitButton.textContent = okName || '确定';
            cancelButton.textContent = cancelName || '取消';
            if(!cancelName) cancelButton.style.display = 'none';
            dialog.style.display = 'block';
            if(modalMode) overlay.style.display = 'block';
            if(typeof onOpen === 'function') onOpen(promptContainer);
            focusEl = promptContainer.querySelector(":focus");
            // 监听按钮点击事件
            submitButton.addEventListener('click', () => {
                dialog.style.display = 'none';
                if(modalMode) overlay.style.display = 'none';
                document.removeEventListener('keydown', handleKeydown);
                if(typeof onSubmit === 'function') {
                    resolve(onSubmit(promptContainer));
                } else {
                    resolve(undefined);
                }
            });
            cancelButton.addEventListener('click', () => {
                dialog.style.display = 'none';
                if(modalMode) overlay.style.display = 'none';
                document.removeEventListener('keydown', handleKeydown);
                resolve(null);
            });
            // 添加键盘事件监听器
            const handleKeydown = () => {
                if (event.key === 'Escape') {
                    cancelButton.click();
                } else if (event.key === 'Enter') {
                    submitButton.click();
                }
            };
            document.addEventListener('keydown', handleKeydown);
            // 监听焦点事件,focus冒泡必须第三参数是true
            promptContainer.addEventListener('focus', (event) => {
                focusEl = event.target;
            }, true);
            // 添加点击遮罩层关闭对话框的功能
            if(modalMode){
                overlay.addEventListener('click', () => {
                    // 点击遮罩层关闭弹窗
                    if(clickOverlayClose) cancelButton.click();
                    // 重新获取焦点
                    if(focusEl) focusEl.focus();
                    // 添加抖动类
                    dialog.classList.add('prompt-shake');
                    // 移除抖动类
                    setTimeout(() => {
                        dialog.classList.remove('prompt-shake');
                    }, 500);
                });
            }
        });
    }

    // 输出到全局变量
    window.showPrompt = showPrompt;
    window.showPromptForm = showPromptForm;
})();

注意事项

  1. showPromptshowPromptForm 的区别,见上面的效果图,上面一个是 showPrompt,这个是对 window.prompt 的模拟,下面一个是 showPromptForm,是对 showPrompt 的扩展,可以用于实现复杂的表单功能。
  2. 默认使用模态模式,如果不使用模态(即不使用遮罩层),调用时把 modalMode 参数设置为 false 即可。
  3. 模态窗口的背景色默认和思源保持一致,如果你想修改背景色,可以修改样式.prompt-overlay 的 background-color: var(--b3-mask-background); 这行代码。
  4. 模态窗口的宽度默认是 400px,如果想自定义宽度可通过 width 参数设置。
  5. 如果想支持 点击遮罩层关闭对话框的功能,把 clickOverlayClose 参数设置为 true 即可。
  6. 点确认按钮返回值是字符串,点取消返回值是 null,但 showPromptForm,点确定的返回值是自己定义的。
  7. 按钮文字可通过 cancelNameokName 参数设置。
  8. 更多功能请参考源码。
  • 思源笔记

    思源笔记是一款隐私优先的个人知识管理系统,支持完全离线使用,同时也支持端到端加密同步。

    融合块、大纲和双向链接,重构你的思维。

    22914 引用 • 92117 回帖
  • 教程
    143 引用 • 611 回帖 • 8 关注
  • 代码
    468 引用 • 631 回帖 • 9 关注
  • JavaScript

    JavaScript 一种动态类型、弱类型、基于原型的直译式脚本语言,内置支持类型。它的解释器被称为 JavaScript 引擎,为浏览器的一部分,广泛用于客户端的脚本语言,最早是在 HTML 网页上使用,用来给 HTML 网页增加动态功能。

    730 引用 • 1328 回帖
3 操作
wilsons 在 2024-08-16 13:36:41 更新了该帖
wilsons 在 2024-08-16 09:41:46 更新了该帖
wilsons 在 2024-08-15 18:58:57 更新了该帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • vincents

    感谢大佬分享 👍 ,下面这样也可以,要安装 open-api 插件,不过简单调用还是你这个方便些。

    const dialog = new openAPI.siyuan.Dialog({
        title: '这里是标题',
        content: `
        <div class="b3-dialog__content">
            <div class="ft__breakword">
                <input type="text" id="ArticleTitle" class="b3-text-field fn__block" style="height: 100%;margin-bottom: 20px;" placeholder='输入标题'>
                <textarea id="ArticleContent" class="b3-text-field fn__block" style="height: 100%;" placeholder='输入内容'></textarea>
            </div>
            <div class="b3-dialog__action" style="padding-right: 0;padding-top: 12px;">
                <button class="b3-button b3-button--cancel" id="CancelBtn">取消</button><div class="fn__space"></div>
                <button class="b3-button b3-button--text" id="ConfirmBtn">确定</button>
            </div>
        </div>
        `,
        width: "500px",
        height: "220px"
    });
    dialog.element.querySelector("#ArticleTitle").focus();
    dialog.element.querySelector("#CancelBtn").addEventListener("click", () => {
        console.log('cancel');
        dialog.destroy();
    });
    dialog.element.querySelector("#ConfirmBtn").addEventListener("click", () => {
        console.log('confirm');
        console.log(dialog.element.querySelector("#ArticleTitle").value);
        console.log(dialog.element.querySelector("#ArticleContent").value);
        dialog.destroy();
    });
    

    dialogdemo.png

    1 回复
  • wilsons

    感谢分享!

    我应该把 prompt api 定义罗列下,这样看起来更清晰

    // 弹出prompt对话框
    // 参数说明
    // message 提示消息,类似标题
    // defaultValue 输入框默认值
    // okName 确认按钮名字
    // cancelName 取消按钮名字
    // width对话框宽度,默认400,高度根据内容自适应
    // modalMode,是否模态窗口,模态窗口,除了当前对话框,其他地方无法点击,false非模态窗,true模态窗
    // clickOverlayClose 是否点击遮罩层关闭对话框,仅在模态窗口下有效
    function showPrompt(message = '', defaultValue = '', okName="确定", cancelName="取消", width=400, modalMode=true, clickOverlayClose=false);
    
    // 弹出自定义prompt对话框
    // 参数说明
    // message 提示消息,类似标题
    // html 对话框自定义表单内容的HTML代码
    // onSubmit 提交时的回调函数,对话框的返回值依赖于这个回调函数的返回值
    // onOpen 打开窗口时的回调函数,可以在打开窗口话进行一些初始化操作,比如某个输入框设为焦点
    // okName 确认按钮名字
    // cancelName 取消按钮名字
    // width 对话框宽度,默认400px,高度根据内容自适应
    // modalMode,是否模态窗口,模态窗口,除了当前对话框,其他地方无法点击,false非模态窗,true模态窗
    // clickOverlayClose 是否点击遮罩层关闭对话框,仅在模态窗口下有效
    function showPromptForm(message = '', html = '', onSubmit = null, onOpen = null, okName="确定", cancelName="取消", width=400, modalMode=true, clickOverlayClose=false);
    
    

推荐标签 标签

  • Typecho

    Typecho 是一款博客程序,它在 GPLv2 许可证下发行,基于 PHP 构建,可以运行在各种平台上,支持多种数据库(MySQL、PostgreSQL、SQLite)。

    12 引用 • 65 回帖 • 445 关注
  • V2Ray
    1 引用 • 15 回帖
  • 招聘

    哪里都缺人,哪里都不缺人。

    190 引用 • 1057 回帖
  • Oracle

    Oracle(甲骨文)公司,全称甲骨文股份有限公司(甲骨文软件系统有限公司),是全球最大的企业级软件公司,总部位于美国加利福尼亚州的红木滩。1989 年正式进入中国市场。2013 年,甲骨文已超越 IBM,成为继 Microsoft 后全球第二大软件公司。

    105 引用 • 127 回帖 • 368 关注
  • Hexo

    Hexo 是一款快速、简洁且高效的博客框架,使用 Node.js 编写。

    21 引用 • 140 回帖 • 5 关注
  • 链滴

    链滴是一个记录生活的地方。

    记录生活,连接点滴

    156 引用 • 3792 回帖 • 3 关注
  • 又拍云

    又拍云是国内领先的 CDN 服务提供商,国家工信部认证通过的“可信云”,乌云众测平台认证的“安全云”,为移动时代的创业者提供新一代的 CDN 加速服务。

    21 引用 • 37 回帖 • 548 关注
  • 黑曜石

    黑曜石是一款强大的知识库工具,支持本地 Markdown 文件编辑,支持双向链接和关系图。

    A second brain, for you, forever.

    16 引用 • 127 回帖
  • flomo

    flomo 是新一代 「卡片笔记」 ,专注在碎片化时代,促进你的记录,帮你积累更多知识资产。

    5 引用 • 107 回帖
  • 区块链

    区块链是分布式数据存储、点对点传输、共识机制、加密算法等计算机技术的新型应用模式。所谓共识机制是区块链系统中实现不同节点之间建立信任、获取权益的数学算法 。

    91 引用 • 751 回帖
  • Netty

    Netty 是一个基于 NIO 的客户端-服务器编程框架,使用 Netty 可以让你快速、简单地开发出一个可维护、高性能的网络应用,例如实现了某种协议的客户、服务端应用。

    49 引用 • 33 回帖 • 23 关注
  • Jenkins

    Jenkins 是一套开源的持续集成工具。它提供了非常丰富的插件,让构建、部署、自动化集成项目变得简单易用。

    53 引用 • 37 回帖 • 1 关注
  • 阿里巴巴

    阿里巴巴网络技术有限公司(简称:阿里巴巴集团)是以曾担任英语教师的马云为首的 18 人,于 1999 年在中国杭州创立,他们相信互联网能够创造公平的竞争环境,让小企业通过创新与科技扩展业务,并在参与国内或全球市场竞争时处于更有利的位置。

    43 引用 • 221 回帖 • 107 关注
  • InfluxDB

    InfluxDB 是一个开源的没有外部依赖的时间序列数据库。适用于记录度量,事件及实时分析。

    2 引用 • 79 关注
  • Spring

    Spring 是一个开源框架,是于 2003 年兴起的一个轻量级的 Java 开发框架,由 Rod Johnson 在其著作《Expert One-On-One J2EE Development and Design》中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 JavaEE 应用程序开发提供集成的框架。

    943 引用 • 1460 回帖 • 5 关注
  • ZeroNet

    ZeroNet 是一个基于比特币加密技术和 BT 网络技术的去中心化的、开放开源的网络和交流系统。

    1 引用 • 21 回帖 • 635 关注
  • Markdown

    Markdown 是一种轻量级标记语言,用户可使用纯文本编辑器来排版文档,最终通过 Markdown 引擎将文档转换为所需格式(比如 HTML、PDF 等)。

    167 引用 • 1520 回帖
  • 服务器

    服务器,也称伺服器,是提供计算服务的设备。由于服务器需要响应服务请求,并进行处理,因此一般来说服务器应具备承担服务并且保障服务的能力。

    125 引用 • 588 回帖
  • 开源

    Open Source, Open Mind, Open Sight, Open Future!

    407 引用 • 3578 回帖
  • 友情链接

    确认过眼神后的灵魂连接,站在链在!

    24 引用 • 373 回帖 • 2 关注
  • 酷鸟浏览器

    安全 · 稳定 · 快速
    为跨境从业人员提供专业的跨境浏览器

    3 引用 • 59 回帖 • 27 关注
  • Love2D

    Love2D 是一个开源的, 跨平台的 2D 游戏引擎。使用纯 Lua 脚本来进行游戏开发。目前支持的平台有 Windows, Mac OS X, Linux, Android 和 iOS。

    14 引用 • 53 回帖 • 536 关注
  • LaTeX

    LaTeX(音译“拉泰赫”)是一种基于 ΤΕΧ 的排版系统,由美国计算机学家莱斯利·兰伯特(Leslie Lamport)在 20 世纪 80 年代初期开发,利用这种格式,即使使用者没有排版和程序设计的知识也可以充分发挥由 TeX 所提供的强大功能,能在几天,甚至几小时内生成很多具有书籍质量的印刷品。对于生成复杂表格和数学公式,这一点表现得尤为突出。因此它非常适用于生成高印刷质量的科技和数学类文档。

    12 引用 • 54 回帖 • 48 关注
  • 支付宝

    支付宝是全球领先的独立第三方支付平台,致力于为广大用户提供安全快速的电子支付/网上支付/安全支付/手机支付体验,及转账收款/水电煤缴费/信用卡还款/AA 收款等生活服务应用。

    29 引用 • 347 回帖 • 3 关注
  • IPFS

    IPFS(InterPlanetary File System,星际文件系统)是永久的、去中心化保存和共享文件的方法,这是一种内容可寻址、版本化、点对点超媒体的分布式协议。请浏览 IPFS 入门笔记了解更多细节。

    21 引用 • 245 回帖 • 241 关注
  • Vue.js

    Vue.js(读音 /vju ː/,类似于 view)是一个构建数据驱动的 Web 界面库。Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。

    265 引用 • 666 回帖 • 1 关注
  • JavaScript

    JavaScript 一种动态类型、弱类型、基于原型的直译式脚本语言,内置支持类型。它的解释器被称为 JavaScript 引擎,为浏览器的一部分,广泛用于客户端的脚本语言,最早是在 HTML 网页上使用,用来给 HTML 网页增加动态功能。

    730 引用 • 1328 回帖