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

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

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

    20974 引用 • 82353 回帖
  • 教程
    143 引用 • 582 回帖 • 8 关注
  • 代码
    463 引用 • 666 回帖 • 8 关注
  • JavaScript

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

    723 引用 • 1300 回帖 • 62 关注
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);
    
    

推荐标签 标签

  • 运维

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

    148 引用 • 257 回帖 • 1 关注
  • 微信

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

    130 引用 • 793 回帖
  • Bug

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

    73 引用 • 1737 回帖
  • 电影

    这是一个不能说的秘密。

    120 引用 • 598 回帖
  • Sphinx

    Sphinx 是一个基于 SQL 的全文检索引擎,可以结合 MySQL、PostgreSQL 做全文搜索,它可以提供比数据库本身更专业的搜索功能,使得应用程序更容易实现专业化的全文检索。

    1 引用 • 198 关注
  • GitBook

    GitBook 使您的团队可以轻松编写和维护高质量的文档。 分享知识,提高团队的工作效率,让用户满意。

    3 引用 • 8 回帖 • 2 关注
  • Chrome

    Chrome 又称 Google 浏览器,是一个由谷歌公司开发的网页浏览器。该浏览器是基于其他开源软件所编写,包括 WebKit,目标是提升稳定性、速度和安全性,并创造出简单且有效率的使用者界面。

    62 引用 • 289 回帖
  • 国际化

    i18n(其来源是英文单词 internationalization 的首末字符 i 和 n,18 为中间的字符数)是“国际化”的简称。对程序来说,国际化是指在不修改代码的情况下,能根据不同语言及地区显示相应的界面。

    8 引用 • 26 回帖 • 2 关注
  • MyBatis

    MyBatis 本是 Apache 软件基金会 的一个开源项目 iBatis,2010 年这个项目由 Apache 软件基金会迁移到了 google code,并且改名为 MyBatis ,2013 年 11 月再次迁移到了 GitHub。

    170 引用 • 414 回帖 • 393 关注
  • PHP

    PHP(Hypertext Preprocessor)是一种开源脚本语言。语法吸收了 C 语言、 Java 和 Perl 的特点,主要适用于 Web 开发领域,据说是世界上最好的编程语言。

    179 引用 • 407 回帖 • 497 关注
  • BAE

    百度应用引擎(Baidu App Engine)提供了 PHP、Java、Python 的执行环境,以及云存储、消息服务、云数据库等全面的云服务。它可以让开发者实现自动地部署和管理应用,并且提供动态扩容和负载均衡的运行环境,让开发者不用考虑高成本的运维工作,只需专注于业务逻辑,大大降低了开发者学习和迁移的成本。

    19 引用 • 75 回帖 • 627 关注
  • NGINX

    NGINX 是一个高性能的 HTTP 和反向代理服务器,也是一个 IMAP/POP3/SMTP 代理服务器。 NGINX 是由 Igor Sysoev 为俄罗斯访问量第二的 Rambler.ru 站点开发的,第一个公开版本 0.1.0 发布于 2004 年 10 月 4 日。

    311 引用 • 546 回帖 • 1 关注
  • 设计模式

    设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

    198 引用 • 120 回帖 • 2 关注
  • Markdown

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

    167 引用 • 1489 回帖 • 1 关注
  • 快应用

    快应用 是基于手机硬件平台的新型应用形态;标准是由主流手机厂商组成的快应用联盟联合制定;快应用标准的诞生将在研发接口、能力接入、开发者服务等层面建设标准平台;以平台化的生态模式对个人开发者和企业开发者全品类开放。

    15 引用 • 127 回帖 • 1 关注
  • 小说

    小说是以刻画人物形象为中心,通过完整的故事情节和环境描写来反映社会生活的文学体裁。

    28 引用 • 108 回帖
  • 30Seconds

    📙 前端知识精选集,包含 HTML、CSS、JavaScript、React、Node、安全等方面,每天仅需 30 秒。

    • 精选常见面试题,帮助您准备下一次面试
    • 精选常见交互,帮助您拥有简洁酷炫的站点
    • 精选有用的 React 片段,帮助你获取最佳实践
    • 精选常见代码集,帮助您提高打码效率
    • 整理前端界的最新资讯,邀您一同探索新世界
    488 引用 • 383 回帖 • 1 关注
  • Flutter

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

    39 引用 • 92 回帖 • 14 关注
  • abitmean

    有点意思就行了

    36 关注
  • CloudFoundry

    Cloud Foundry 是 VMware 推出的业界第一个开源 PaaS 云平台,它支持多种框架、语言、运行时环境、云平台及应用服务,使开发人员能够在几秒钟内进行应用程序的部署和扩展,无需担心任何基础架构的问题。

    5 引用 • 18 回帖 • 155 关注
  • FFmpeg

    FFmpeg 是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。

    23 引用 • 32 回帖 • 5 关注
  • 锤子科技

    锤子科技(Smartisan)成立于 2012 年 5 月,是一家制造移动互联网终端设备的公司,公司的使命是用完美主义的工匠精神,打造用户体验一流的数码消费类产品(智能手机为主),改善人们的生活质量。

    4 引用 • 31 回帖
  • 小薇

    小薇是一个用 Java 写的 QQ 聊天机器人 Web 服务,可以用于社群互动。

    由于 Smart QQ 从 2019 年 1 月 1 日起停止服务,所以该项目也已经停止维护了!

    34 引用 • 467 回帖 • 722 关注
  • CSS

    CSS(Cascading Style Sheet)“层叠样式表”是用于控制网页样式并允许将样式信息与网页内容分离的一种标记性语言。

    191 引用 • 509 回帖
  • HHKB

    HHKB 是富士通的 Happy Hacking 系列电容键盘。电容键盘即无接点静电电容式键盘(Capacitive Keyboard)。

    5 引用 • 74 回帖 • 448 关注
  • 宕机

    宕机,多指一些网站、游戏、网络应用等服务器一种区别于正常运行的状态,也叫“Down 机”、“当机”或“死机”。宕机状态不仅仅是指服务器“挂掉了”、“死机了”状态,也包括服务器假死、停用、关闭等一些原因而导致出现的不能够正常运行的状态。

    13 引用 • 82 回帖 • 47 关注
  • GitLab

    GitLab 是利用 Ruby 一个开源的版本管理系统,实现一个自托管的 Git 项目仓库,可通过 Web 界面操作公开或私有项目。

    46 引用 • 72 回帖