首页 > 其他分享 >custom:用户自定义插件,提供开放能力

custom:用户自定义插件,提供开放能力

时间:2024-05-27 13:01:01浏览次数:14  
标签:插件 自定义 plugin custom 右键 hotkey config

custom 插件的功能:支持用户 在右键菜单中自定义插件

简介

custom 插件大量采用声明式代码(声明代替代码开发),比如:

  • 只需使用 style = () => "...",即可注册 css。
  • 只需使用 styleTemplate = () => ({renderArg}),即可引入 css 文件,并且支持渲染模板。
  • 只需使用 html = () => "...",即可注册 HTML 元素。
  • 只需使用 hint = () => "将当前标题的路径复制到剪切板",即可注册 hint。
  • 只需使用 selector = () => "...",即可注册允许运行命令的光标位置。
  • 只需使用 hotkey = () => ["ctrl+shift+y"] ,即可注册快捷键。
  • 只需使用 this.modal 函数即可自动生成自定义的模态框。
  • init、html、process、callback 等等生命周期函数。
class fullPathCopy extends BaseCustomPlugin {
    style = () => "..."
    html = () => "..."
    hint = () => "将当前标题的路径复制到剪切板"
    hotkey = () => ["ctrl+shift+y"]
    callback = anchorNode => {
        const modal = {
            title: "这是模态框标题",
            components: [
                { label: "input的label", type: "input", value: "input的默认value", placeholder: "nput的placeholder" },
                // password、textarea、checkbox、radio、select
                ...
            ]
        };
        const callback = response => console.log(response)
        this.modal(modal, callback)
    }
}

如何使用

仅需两步:

  1. ./plugin/custom/custom_plugin.user.toml 添加配置。
  2. ./plugin/custom/plugins 目录下,创建和插件同名的 js 文件,在此文件中创建一个 class 继承自 BaseCustomPlugin,并导出为 plugin

示例一:快速开始

您可以根据下面的步骤,先把插件跑起来。

步骤一:在 ./plugin/custom/custom_plugin.user.toml 添加如下配置:

[helloWorld]
name = "你好世界"   # 右键菜单中展示的名称
enable = true     # 是否启用此二级插件
hide = false      # 是否在右键菜单中隐藏
order = 1         # 在右键菜单中的出现顺序(越大越排到后面,允许负数)

	[helloWorld.config]
	hotkey_string = "ctrl+alt+u"
	console_message = "i am in process"
	show_message = "this is hello world plugin"

如果您对 TOML 不太了解,可以花三分钟了解 TOML 教程

步骤二:创建文件 ./plugin/custom/plugins/helloWorld.js 文件,将下面代码保存到该文件中:

// ./plugin/custom/plugins/helloWorld.js

class helloWorld extends BaseCustomPlugin {
    hint = () => "this is hello world hint"

    hotkey = () => [this.config.hotkey_string]

    // process方法会在插件初始化时自动运行
    process = () => {
        console.log(this.config.console_message);
        console.log("[helloWorldPlugin]:", this);
    }

    // callback方法会在插件被调用时运行,参数anchorNode:调用此插件时,鼠标光标所在的Element
    callback = anchorNode => {
        alert(this.config.show_message);
    }
}

module.exports = { plugin: helloWorld };

步骤三:验证,完成以上步骤后,重启 Typora。

  1. 打开【检查元素】,发现控制台输出了 i am in process 和 插件对象。你可以仔细看看 this 自带的属性和方法。
  2. 鼠标右键,弹出菜单,鼠标悬停在 常用插件 -> 二级插件 -> 你好世界。发现出现了 hint,显示 this is hello world hint。点击 你好世界,发现弹出了提示框,显示 this is hello world plugin
  3. 键入快捷键 ctrl+alt+u,发现弹出了同样的提示框。

示例二:简易功能

此示例来自 此 issue,简易实现了一个轻量化的需求,旨在让用户了解如何实现一个插件。

需求:md 文档中有几百行的复选框(任务列表)内容,一个个点选中或取消,效率太低了。希望开发全选、反选复选框功能的插件。

步骤一:打开文件 plugin\global\settings\custom_plugin.user.toml,添加下面内容:

[selectCheckboxes]
name = "反选复选框"       # 右键菜单中展示的名称
enable = true           # 是否启用此二级插件
hide = false            # 是否在右键菜单中隐藏
order = 1               # 在右键菜单中的出现顺序(越大越排到后面,允许负数)

    [selectCheckboxes.config]
    select_all_hotkey = "ctrl+alt+h"      # 全选快捷键
    select_none_hotkey = "ctrl+alt+j"     # 取消全部选择快捷键
    select_reverse_hotkey = "ctrl+alt+k"  # 反选快捷键

步骤二:打开目录 plugin\custom\plugins,在此目录下创建文件 selectCheckboxes.js,写入如下内容:

class selectCheckboxes extends BaseCustomPlugin {
    rangeTaskListItem = fn => document.querySelectorAll('.md-task-list-item input[type="checkbox"]').forEach(fn)

    selectAll = input => !input.checked && input.click()
    selectNone = input => input.checked && input.click()
    selectReverse = input => input.click()

    process = () => {
        const { select_all_hotkey, select_none_hotkey, select_reverse_hotkey } = this.config;
        this.utils.registerHotkey([
            { hotkey: select_all_hotkey, callback: () => this.rangeTaskListItem(this.selectAll) },
            { hotkey: select_none_hotkey, callback: () => this.rangeTaskListItem(this.selectNone) },
            { hotkey: select_reverse_hotkey, callback: () => this.rangeTaskListItem(this.selectReverse) },
        ])
    }

    callback = () => this.rangeTaskListItem(this.selectReverse)
}

module.exports = { plugin: selectCheckboxes };

步骤三:重启 Typora,创建一个带有任务列表的 md 文件,尝试以下操作:

示例三:实战

需求如下:

  1. 在右键菜单中添加一个 获取标题路径 (类似于 messing.md\无 一级标题\开放平台 二级标题\window_tab 三级标题),然后将其写入剪切板。
  2. 当光标位于【正文标题】中才可使用。
  3. 快捷键 ctrl+shift+u

实现:

步骤一:修改 ./plugin/global/settings/custom_plugin.user.toml,添加配置:

  • name:(必选)右键菜单中展示的名称
  • enable:(必选)是否启用此插件
  • hide:(可选)是否在右键菜单中隐藏,默认为 false
  • order:(可选)在右键菜单中的出现顺序(越大越排到后面,允许负数),默认为 1
  • config:(可选)插件自己的配置,这里的内容将被封装为 插件类的 this.config 属性
# ./plugin/global/settings/custom_plugin.user.toml

[myFullPathCopy]
name = "我的复制标题路径插件"
enable = true
hide = false
order = 1

    # 插件配置
    [myFullPathCopy.config]
    # 快捷键
    hotkey = "ctrl+shift+u"
    # 如果在空白页调用此插件,使用的文件名(因为尚不存在该文件,需要用一个默认的文件名代替)
    untitled_file_name = "untitled"
    # 跳过空白的标题
    ignore_empty_header = false
    # 标题和提示之前添加空格
    add_space = true
    # 使用绝对路径
    full_file_path = false

步骤二:在 ./plugin/custom/plugins 目录下,创建和插件同名的 js 文件(myFullPathCopy.js),在此文件中创建一个 class 继承自 BaseCustomPlugin,并导出为 plugin

// ./plugin/custom/plugins/myFullPathCopy.js

// 1
class myFullPathCopy extends BaseCustomPlugin {
    // 2
    selector = () => "#write h1, h2, h3, h4, h5, h6"
    // 3
    hint = () => "将当前标题的路径复制到剪切板"
    // 4
    init = () => {}
    // 5
    style = () => {}
    // 6
    styleTemplate = () => {}
    // 7
    html = () => {}
    // 8
    hotkey = () => [this.config.hotkey]
    // 9
    beforeProcess = async () => {}
    // 10
    process = () => {}
    // 11
    callback = anchorNode => {
        const paragraphList = ["H1", "H2", "H3", "H4", "H5", "H6"];
        const nameList = ["一级标题", "二级标题", "三级标题", "四级标题", "五级标题", "六级标题"];
        const pList = [];
        let ele = anchorNode;

        while (ele) {
            const idx = paragraphList.indexOf(ele.tagName);
            if (idx !== -1) {
                if (pList.length === 0 || (pList[pList.length - 1].idx > idx)) {
                    pList.push({ele, idx})
                    if (pList[pList.length - 1].idx === 0) break;
                }
            }
            ele = ele.previousElementSibling;
        }

        pList.reverse();

        let filePath = (this.config.full_file_path) ? this.utils.getFilePath() : File.getFileName();
        filePath = filePath || this.config.untitled_file_name;
        const result = [filePath];
        let headerIdx = 0;
        for (const p of pList) {
            while (headerIdx < 6 && p.ele.tagName !== paragraphList[headerIdx]) {
                if (!this.config.ignore_empty_header) {
                    const name = this.getHeaderName("无", nameList[headerIdx]);
                    result.push(name);
                }
                headerIdx++;
            }

            if (p.ele.tagName === paragraphList[headerIdx]) {
                const name = this.getHeaderName(p.ele.querySelector("span").textContent, nameList[headerIdx]);
                result.push(name);
                headerIdx++;
            }
        }

        const text = this.utils.Package.Path.join(...result);
        navigator.clipboard.writeText(text);
    }

    getHeaderName = (title, name) => {
        const space = this.config.add_space ? " " : "";
        return title + space + name
    }
}

// 12
module.exports = { plugin: myFullPathCopy };

// 1. 创建 class,继承 BaseCustomPlugin 类。之后 myFullPathCopy 将自动拥有 uploadArticle、info、config 属性。
//    - uploadArticle:插件系统自带的静态工具类,其定义在 `./plugin/global/core/plugin.js/uploadArticle`。其中有三个重要的函数:uploadArticle.getPlugin(fixedName) 和 uploadArticle.getCustomPlugin(fixedName) 用于获取已经实现的全部插件,调用其 API,具体的 API 可看 ./plugin/custom/openPlatformAPI.md 文件。uploadArticle.addEventListener(eventType, listener) 用于监听 Typora 的生命周期事件。
//    - info:该插件在 custom_plugin.user.toml 里的所有字段
//    - config:等同于 info.config,即配置文件里的 config 属性
// 2. selector:允许运行命令的光标位置:当光标位于哪些位置时,此命令才可用。返回 null-like value 表示任何位置都可用,在这里的含义就是:只当光标位于【正文标题】时可用
// 3. hint:当鼠标移动到右键菜单时的提示
// 4. init:在这里初始化你要的变量
// 5. style:给 Typora 插入 style 标签。返回值为 string。若你想指定标签的 id,也可以返回 {textID: "", text: ""}。其中 textID 为此 style 标签的 id,text 为 style 内容。
// 6. styleTemplate: 引入 `./plugin/global/user_styles` 目录下和插件同名的 css 文件。详情请参考`user_styles/请读我.md`
// 7. html:为 Typora 插入 HTML 标签,返回 Element 类型或者 Element-string。
// 8. hotkey:为 callabck 注册快捷键,返回 Array<string> 类型。
// 9. beforeProcess:最先执行的函数,在这里初始化插件需要的数据。若返回 this.uploadArticle.stopLoadPluginError,则停止加载插件
// 10. process:在这里添加添加插件业务逻辑,比如添加 listener 和修改 Typora 的第一方函数
// 11. callback:右键菜单中点击/键入快捷键后的回调函数。anchorNode 参数: 鼠标光标所在的 Element
// 12. export:导出名为 plugin

验证:

打开 Typora,将光标置于正文标题出,右键弹出菜单,常用插件 -> 二级插件 -> 复制标题路径,点击。当前的标题路径就写入剪切板了。在目标文档区域,粘贴,即可把标题路径复制到相应文档区域。

示例四:参考

如果您有心自己写插件,可以参考 noImageMode 插件,仅有 30+ 行代码。

./plugin/custom/plugins/noImageMode.js

标签:插件,自定义,plugin,custom,右键,hotkey,config
From: https://www.cnblogs.com/bug-designer-cn/p/18215271

相关文章

  • 【BI 可视化插件】怎么做? 手把手教你实现
    背景对于现在的用户来说,插件已经成为一个熟悉的概念。无论是在使用软件、IDE还是浏览器时,插件都是为了在原有产品基础上提供更多更便利的操作。在BI领域,图表的丰富性和对接各种场景的自定义是最吸引人的特点。虽然市面上现有的BI软件内置了许多图表组件和自定义属性设置,但......
  • Vue3标签组件绘制--自定义按钮组件
    不知道怎么的,突然想绘制一个标签,比如el-button什么的。今天研究一下吧,不知道能不能整出来以后就可以绘制自己的组件,弄自己的组件库了。不知道有朝一日能不能让越组件青史留名?嘻嘻,百日梦做差不多了,接着去查查资料。文章分为三个部分:1.按钮组件实现(根据查阅的一个文章实现基......
  • 【C语言】自定义类型:联合与枚举的简明概述
    ......
  • Qt QListWidget 存放自定义控件不显示问题
    问题软件功能:每点击一次新建按钮,在QListWidget新增一行自定义控件,主窗口和自定义窗口如下。主窗口:自定义窗口问题代码:Form*myform=newForm();QListWidgetItem*item=newQListWidgetItem(ui->listWidget);ui->listWidget->addItem(item);......
  • 这个让ChatGPT变强的功能还有多少人不知道(自定义ChatGPT)!
    ChatGPT已经成为许多人日常生活和工作中的重要工具。为了更好地满足用户的个性化需求,ChatGPT推出了自定义功能。但是最近我发现很多同学因为嫌麻烦或者不知道怎么写,就没有使用过这个功能,也有些同学都不知道这个功能。所以今天我就来科普一下怎么使用这个功能使你的ChatGPT......
  • kubectl自动补全插件
    1.安装bashcompletionyuminstall-ybash-completion2.修改配置补全脚本在文件~/.bashrc中导入(source)补全脚本:echo'source<(kubectlcompletionbash)'>>~/.bashrc将补全脚本添加到目录/etc/bash_completion.d中:kubectlcompletionbash>/etc/bash_comp......
  • 网易云音乐插件使用教程,地址:music.163.com
    参看官方教程:点击“生成外链播放器”链接,跳转到选择自己喜欢的播放器风格及尺寸,目前官方支持两个风格勾选是否自动播放。是,别人访问网站时播放器会自动开始播放;否,别人访问网站时播放器需要手动播放音乐......
  • 自定义类型:联合和枚举
    目录1.联合体1.1联合体类型的声明1.2联合体的特点1.3相同成员的结构体和联合体对比1.4联合体大小的计算1.5联合的⼀个练习2.枚举类型2.1枚举类型的声明2.2枚举类型的优点2.3枚举类型的使用1.联合体1.1联合体类型的声明像结构体⼀样,联合体也是由⼀个或......
  • 自定义RedisTemplate,解决Redis乱码问题
    问题:使用默认的RedisTemplate来操作Redis,在其底层使用的是JDK序列化器,会导致数据乱码问题,可读性差,其优点是兼容性高。解决:自定义RedisTemplate,使用Jackson序列化器替代JDK序列化器。@ConfigurationpublicclassRedisConfig{@BeanpublicRedisTemplate<String,......
  • 7.2k star的万能视频解析下载插件
    今天给大家介绍一个超级厉害的浏览器插件,可以解析各个平台网页视频——猫抓。项目简介猫抓(cat-catch)是一款资源嗅探扩展插件,他能够帮助你筛选列出当前页面的资源。简单来说,当你打开任意一个带有视频的网页,猫抓就可以解析视频的真实地址,协助你下载视频。猫抓这个名字很有......