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. 更多功能请参考源码。
  • 思源笔记

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

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

    22250 引用 • 88929 回帖
  • 教程
    143 引用 • 598 回帖 • 8 关注
  • 代码
    466 引用 • 631 回帖 • 9 关注
  • JavaScript

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

    729 引用 • 1327 回帖 • 1 关注
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);
    
    

推荐标签 标签

  • 知乎

    知乎是网络问答社区,连接各行各业的用户。用户分享着彼此的知识、经验和见解,为中文互联网源源不断地提供多种多样的信息。

    10 引用 • 66 回帖 • 1 关注
  • 又拍云

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

    21 引用 • 37 回帖 • 546 关注
  • RYMCU

    RYMCU 致力于打造一个即严谨又活泼、专业又不失有趣,为数百万人服务的开源嵌入式知识学习交流平台。

    4 引用 • 6 回帖 • 53 关注
  • MySQL

    MySQL 是一个关系型数据库管理系统,由瑞典 MySQL AB 公司开发,目前属于 Oracle 公司。MySQL 是最流行的关系型数据库管理系统之一。

    685 引用 • 535 回帖
  • FlowUs

    FlowUs.息流 个人及团队的新一代生产力工具。

    让复杂的信息管理更轻松、自由、充满创意。

    1 引用 • 1 关注
  • Mobi.css

    Mobi.css is a lightweight, flexible CSS framework that focus on mobile.

    1 引用 • 6 回帖 • 740 关注
  • 单点登录

    单点登录(Single Sign On)是目前比较流行的企业业务整合的解决方案之一。SSO 的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

    9 引用 • 25 回帖
  • 黑曜石

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

    A second brain, for you, forever.

    15 引用 • 122 回帖
  • Flume

    Flume 是一套分布式的、可靠的,可用于有效地收集、聚合和搬运大量日志数据的服务架构。

    9 引用 • 6 回帖 • 628 关注
  • Postman

    Postman 是一款简单好用的 HTTP API 调试工具。

    4 引用 • 3 回帖
  • Notion

    Notion - The all-in-one workspace for your notes, tasks, wikis, and databases.

    6 引用 • 38 回帖
  • 博客

    记录并分享人生的经历。

    273 引用 • 2388 回帖
  • 微信

    腾讯公司 2011 年 1 月 21 日推出的一款手机通讯软件。用户可以通过摇一摇、搜索号码、扫描二维码等添加好友和关注公众平台,同时可以将自己看到的精彩内容分享到微信朋友圈。

    130 引用 • 793 回帖 • 1 关注
  • OpenStack

    OpenStack 是一个云操作系统,通过数据中心可控制大型的计算、存储、网络等资源池。所有的管理通过前端界面管理员就可以完成,同样也可以通过 Web 接口让最终用户部署资源。

    10 引用 • 4 关注
  • 七牛云

    七牛云是国内领先的企业级公有云服务商,致力于打造以数据为核心的场景化 PaaS 服务。围绕富媒体场景,七牛先后推出了对象存储,融合 CDN 加速,数据通用处理,内容反垃圾服务,以及直播云服务等。

    26 引用 • 222 回帖 • 170 关注
  • LeetCode

    LeetCode(力扣)是一个全球极客挚爱的高质量技术成长平台,想要学习和提升专业能力从这里开始,充足技术干货等你来啃,轻松拿下 Dream Offer!

    209 引用 • 72 回帖
  • WebSocket

    WebSocket 是 HTML5 中定义的一种新协议,它实现了浏览器与服务器之间的全双工通信(full-duplex)。

    48 引用 • 206 回帖 • 334 关注
  • frp

    frp 是一个可用于内网穿透的高性能的反向代理应用,支持 TCP、UDP、 HTTP 和 HTTPS 协议。

    20 引用 • 7 回帖 • 3 关注
  • Vue.js

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

    266 引用 • 665 回帖 • 2 关注
  • Bug

    Bug 本意是指臭虫、缺陷、损坏、犯贫、窃听器、小虫等。现在人们把在程序中一些缺陷或问题统称为 bug(漏洞)。

    75 引用 • 1737 回帖 • 4 关注
  • SOHO

    为成为自由职业者在家办公而努力吧!

    7 引用 • 55 回帖 • 21 关注
  • Google

    Google(Google Inc.,NASDAQ:GOOG)是一家美国上市公司(公有股份公司),于 1998 年 9 月 7 日以私有股份公司的形式创立,设计并管理一个互联网搜索引擎。Google 公司的总部称作“Googleplex”,它位于加利福尼亚山景城。Google 目前被公认为是全球规模最大的搜索引擎,它提供了简单易用的免费服务。不作恶(Don't be evil)是谷歌公司的一项非正式的公司口号。

    49 引用 • 192 回帖 • 2 关注
  • 运维

    互联网运维工作,以服务为中心,以稳定、安全、高效为三个基本点,确保公司的互联网业务能够 7×24 小时为用户提供高质量的服务。

    149 引用 • 257 回帖
  • danl
    131 关注
  • Lute

    Lute 是一款结构化的 Markdown 引擎,支持 Go 和 JavaScript。

    25 引用 • 191 回帖 • 17 关注
  • Latke

    Latke 是一款以 JSON 为主的 Java Web 框架。

    71 引用 • 535 回帖 • 780 关注
  • Flutter

    Flutter 是谷歌的移动 UI 框架,可以快速在 iOS 和 Android 上构建高质量的原生用户界面。 Flutter 可以与现有的代码一起工作,它正在被越来越多的开发者和组织使用,并且 Flutter 是完全免费、开源的。

    39 引用 • 92 回帖 • 4 关注