[js] 不用插件:绑定思源事件总线(eventBus)

不多说了,用的人都懂。

简洁版 (不推荐,有风险)

优点:代码简洁。

缺点:要求用户至少安装一个插件。

⚠️ 风险警告:当用户关闭第一个插件时,之前的绑定会失效。

function eventBusOn(eventName, callback) {
    const plugin = window.siyuan.ws.app.plugins[0];
    if(!plugin) {
        console.log('绑定事件'+eventName+'失败,请至少安装一个插件');
        return false;
    }
    plugin.eventBus.on(eventName, callback);
    return true;
}
function eventBusOff(eventName, callback) {
    const plugin = window.siyuan.ws.app.plugins[0];
    if(!plugin) {
        console.log('解绑事件'+eventName+'失败,请至少安装一个插件');
        return false;
    }
    plugin.eventBus.off(eventName, callback);
    return true;
}

改进版

优点:兼容性好。

缺点:代码量大。

// 参考 https://github.com/siyuan-note/siyuan/blob/f660b2ddfa98f93f778bbbbe5abc3bfaa5a932b6/app/src/plugin/index.ts
function eventBusOn(eventName, callback) {
    const plugin = getMyPlugin();
    plugin.eventBus.on(eventName, callback);
}
function eventBusOff(eventName, callback) {
    const plugin = getMyPlugin();
    plugin.eventBus.off(eventName, callback);
}
function getMyPlugin(pluginName = "my-custom-plugin") {
    let myPlugin = window.siyuan.ws.app.plugins.find(item=>item.name === pluginName);
    if(myPlugin) return myPlugin;
    class EventBus {
        constructor(name = "") {
            this.eventTarget = document.createComment(name);
            document.appendChild(this.eventTarget);
        }
        on(type, listener) {
            this.eventTarget.addEventListener(type, listener);
        }
        once(type, listener) {
            this.eventTarget.addEventListener(type, listener, { once: true });
        }
        off(type, listener) {
            this.eventTarget.removeEventListener(type, listener);
        }
        emit(type, detail) {
            return this.eventTarget.dispatchEvent(new CustomEvent(type, { detail, cancelable: true }));
        }
    }
    class Plugin {
        constructor(options) {
            this.app = options.app||window.siyuan.ws.app.appId;
            this.i18n = options.i18n;
            this.displayName = options.displayName||options.name;
            this.name = options.name;
            this.eventBus = new EventBus(options.name);
            this.protyleSlash = [];
            this.customBlockRenders = {};
            this.topBarIcons = [];
            this.statusBarIcons = [];
            this.commands = [];
            this.models = {};
            this.docks = {};
            this.data = {};
            this.protyleOptionsValue = null;
        }
        onload() {}
        onunload() {}
        uninstall() {}
        async updateCards(options) { return options; } // 返回选项本身
        onLayoutReady() {}
        addCommand(command) {}
        addIcons(svg) {}
        addTopBar(options) { return null; } // 模拟返回null
        addStatusBar(options) { return null; } // 模拟返回null
        // 去掉设置,参考 https://github.com/siyuan-note/siyuan/blob/dae6158860cc704e353454565c96e874278c6f47/app/src/plugin/openTopBarMenu.ts#L25
        //openSetting() {}
        loadData(storageName) { return Promise.resolve(null); }
        saveData(storageName, data) { return Promise.resolve(); }
        removeData(storageName) { return Promise.resolve(); }
        getOpenedTab() { return {}; } // 返回空对象
        addTab(options) { return () => {}; } // 返回空函数模拟模型
        addDock(options) { return {}; } // 返回空对象模拟 dock
        addFloatLayer(options) {}
        updateProtyleToolbar(toolbar) { return toolbar; } // 返回toolbar本身
        set protyleOptions(options) {}
        get protyleOptions() { return this.protyleOptionsValue; }
    }
    myPlugin = new Plugin({name:pluginName});
    window.siyuan.ws.app.plugins.push(myPlugin);
    return myPlugin;
}

已封装到 openAny 中。

[js] 连续点击 openAny,小代码,大作用,让一切触手可达

在 openAny 中调用方法示例:

const handler = (args)=>console.log(args);

openAny.on('open-menu-link', handler);

openAny.once('open-menu-link', handler);

openAny.off('open-menu-link', handler);

openAny.emit('your event name', data);
  • 思源笔记

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

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

    26013 引用 • 107951 回帖
  • 代码片段

    代码片段分为 CSS 与 JS 两种代码,添加在 [设置 - 外观 - 代码片段] 中,这些代码会在思源笔记加载时自动执行,用于改善笔记的样式或功能。

    用户在该标签下分享代码片段时需在帖子标题前添加 [css] [js] 用于区分代码片段类型。

    186 引用 • 1314 回帖

相关帖子

欢迎来到这里!

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

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

    第一时间想的是能在主题里做什么,想了一段时间之后还是脑袋空空,不过可扩展性很大

    1 回复
  • wilsons

    是的,如果愿意,还可以把插件的其他功能加进来。

    如果本来就是插件的话题,这个用不到其实。

    1 回复
  • EmptyLight

    如果在开发环境里面用的是 ts,应该可以把插件的 d.ts 定义文件引入进来,在编辑插件 API 部分获得类似插件的开发效果

    1 回复
  • wilsons

    是的,只要不报错,仅提供需要的实现即可,达到轻量级目的。

    我刚开始本来打算用代理,仅有 evenBus 属性实现,但发现思源默认插件方法属性返回值等都是存在的,如果兼容做好,代码量和实现空类差不多,甚至更多,关键可读性还不好。

  • emmmm,集市里有一款插件叫 OpenAPIdoge

    1 回复
  • wilsons

    是的,runjs 也支持。

    但这些需要依赖用户安装了插件,自用还不错。

    这个好处是可以不依赖其他插件,后面会集成到 openAny 中。😀

    尚未发现副作用。

  • w 佬,请问如何通过 js 实现通过快捷键对当前 tab 或者弹出的思源窗口设置成右侧(下侧)分屏?

    1 回复
  • wilsons

    openAny 可以实现。

    另外你这里的“或”是指实现任意一个就行吗?还是“和”?

    弹出的思源窗口是指悬浮窗?比如鼠标悬浮到引用上时弹出的悬浮窗?

    1 回复
  • 主要是悬浮的, 比如一个 引用笔记, 或者提及, 都是悬浮打开的, 这时如果能快捷打开到 分屏 就很方便了.

    1 回复
  • wilsons

    悬浮窗分屏显示

    需要先安装 openAny [js] 连续点击 openAny,小代码,大作用,让一切触手可达

    // 一键让悬浮窗分屏显示
    setTimeout(()=>{
        // 设置分屏方向 right 向右 down 向下
        const splitTo = 'right';
      
        // 设置快捷键
        const pressKey = 'alt+z';
      
        openAny.setKeymap(pressKey, async (event, {sleep, whenElementExist}) => {
            event.preventDefault();
            event.stopPropagation();
            // 函数嵌套中最好使用新实例,避免内外相互等待而死锁
            const openAny = new OpenAny();
            // 把悬浮窗变为标签
            await openAny.click('.block__popover [data-type="stickTab"]');
            // 等待把悬浮窗变为标签
            await sleep(100);
            // 获取悬浮窗标签
            const tab = await whenElementExist(()=>document.querySelector('[data-type="wnd"].layout__wnd--active .layout-tab-bar li.item--focus')||document.querySelector('[data-type="wnd"] .layout-tab-bar li.item--focus'));
            // 打开悬浮窗标签右键菜单
            await openAny.press('mouseright', tab);
            // 选择菜单中的分屏方式
            await openAny.click(`#commonMenu [data-id="splitMove${splitTo==='down'?'B':'R'}"]`);
        });
    }, 2000);
    
    1 回复
  • 大神啊, 太感谢了.正是我要的.

    我在本论坛多次要求作者实现一个类似的快速分屏的办法, 每次都被各种拒绝.

  • 我目前发现两个问题,请确认一下:

    1. updateProtyleToolbar(toolbar) 需要原样返回:updateProtyleToolbar(toolbar) { return toolbar; },返回空数组的话会清空工具栏,导致工具栏无法显示。相关代码:https://github.com/siyuan-note/siyuan/blob/f660b2ddfa98f93f778bbbbe5abc3bfaa5a932b6/app/src/protyle/toolbar/index.ts#L70

    2. openSetting() {} 执行之后会在右上角的插件菜单添加一个空选项

      image.png

    1 回复
    1 操作
    JeffreyChen 在 2025-06-04 19:05:32 更新了该回帖
  • wilsons 1 1 评论

    感谢反馈!这两个问题已解决!

    我对比了一遍原 plugin 对象的属性和方法,应该没有问题了。

    主要修改点

    image.png

    你的代码注释没改
    JeffreyChen 1 赞同
请输入回帖内容 ...
wilsons
正在努力开发 wilsons 工具箱中 🛠️ 目前已正式入驻爱发电啦!💖 想催更、提需求?欢迎访问 👉 https://afdian.com/a/wilsons

推荐标签 标签

  • RemNote
    2 引用 • 16 回帖 • 20 关注
  • 黑曜石

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

    A second brain, for you, forever.

    24 引用 • 242 回帖 • 2 关注
  • 安全

    安全永远都不是一个小问题。

    199 引用 • 818 回帖
  • Markdown

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

    171 引用 • 1537 回帖
  • 互联网

    互联网(Internet),又称网际网络,或音译因特网、英特网。互联网始于 1969 年美国的阿帕网,是网络与网络之间所串连成的庞大网络,这些网络以一组通用的协议相连,形成逻辑上的单一巨大国际网络。

    98 引用 • 367 回帖
  • OAuth

    OAuth 协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是 oAuth 的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此 oAuth 是安全的。oAuth 是 Open Authorization 的简写。

    36 引用 • 103 回帖 • 37 关注
  • Access
    1 引用 • 3 回帖 • 3 关注
  • HTML

    HTML5 是 HTML 下一个的主要修订版本,现在仍处于发展阶段。广义论及 HTML5 时,实际指的是包括 HTML、CSS 和 JavaScript 在内的一套技术组合。

    108 引用 • 295 回帖 • 1 关注
  • 开源

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

    412 引用 • 3588 回帖
  • 锤子科技

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

    4 引用 • 31 回帖 • 1 关注
  • Jenkins

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

    54 引用 • 37 回帖 • 2 关注
  • CongSec

    本标签主要用于分享网络空间安全专业的学习笔记

    1 引用 • 1 回帖 • 37 关注
  • 服务

    提供一个服务绝不仅仅是简单的把硬件和软件累加在一起,它包括了服务的可靠性、服务的标准化、以及对服务的监控、维护、技术支持等。

    41 引用 • 24 回帖
  • webpack

    webpack 是一个用于前端开发的模块加载器和打包工具,它能把各种资源,例如 JS、CSS(less/sass)、图片等都作为模块来使用和处理。

    42 引用 • 130 回帖 • 252 关注
  • SQLite

    SQLite 是一个进程内的库,实现了自给自足的、无服务器的、零配置的、事务性的 SQL 数据库引擎。SQLite 是全世界使用最为广泛的数据库引擎。

    4 引用 • 7 回帖 • 5 关注
  • Quicker

    Quicker 您的指尖工具箱!操作更少,收获更多!

    37 引用 • 157 回帖 • 1 关注
  • ZeroNet

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

    1 引用 • 21 回帖 • 654 关注
  • SVN

    SVN 是 Subversion 的简称,是一个开放源代码的版本控制系统,相较于 RCS、CVS,它采用了分支管理系统,它的设计目标就是取代 CVS。

    29 引用 • 98 回帖 • 694 关注
  • Git

    Git 是 Linux Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。

    211 引用 • 358 回帖
  • WebClipper

    Web Clipper 是一款浏览器剪藏扩展,它可以帮助你把网页内容剪藏到本地。

    3 引用 • 9 回帖 • 1 关注
  • SpaceVim

    SpaceVim 是一个社区驱动的模块化 vim/neovim 配置集合,以模块的方式组织管理插件以
    及相关配置,为不同的语言开发量身定制了相关的开发模块,该模块提供代码自动补全,
    语法检查、格式化、调试、REPL 等特性。用户仅需载入相关语言的模块即可得到一个开箱
    即用的 Vim-IDE。

    3 引用 • 31 回帖 • 110 关注
  • WiFiDog

    WiFiDog 是一套开源的无线热点认证管理工具,主要功能包括:位置相关的内容递送;用户认证和授权;集中式网络监控。

    1 引用 • 7 回帖 • 613 关注
  • gRpc
    11 引用 • 9 回帖 • 100 关注
  • GitBook

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

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

    Tomcat 最早是由 Sun Microsystems 开发的一个 Servlet 容器,在 1999 年被捐献给 ASF(Apache Software Foundation),隶属于 Jakarta 项目,现在已经独立为一个顶级项目。Tomcat 主要实现了 JavaEE 中的 Servlet、JSP 规范,同时也提供 HTTP 服务,是市场上非常流行的 Java Web 容器。

    162 引用 • 529 回帖 • 3 关注
  • WebSocket

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

    48 引用 • 206 回帖 • 286 关注
  • 又拍云

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

    20 引用 • 37 回帖 • 578 关注