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

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

简介

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

  • 只需使用 style = () => "...",即可注册 css。

  • 只需使用 styleTemplate = () => ({renderArg}),即可引入 css 文件,并且支持渲染模板。

  • 只需使用 html = () => "...",即可注册 HTML 元素。

  • 只需使用 hint = () => "将当前标题的路径复制到剪切板",即可注册 hint。

  • 只需使用 selector = () => "...",即可注册允许运行命令的光标位置。

  • 只需使用 hotkey = () => ["ctrl+shift+y"] ,即可注册快捷键。

  • getSign = (uuid, url) => {
        const parsedUrl = new URL(url);
        const _url = parsedUrl.pathname;
    
        const ekey = "9znpamsyl2c7cdrr9sas0le9vbc3r6ba";
        const xCaKey = "203803574"
        const toEnc = `POST\napplication/json, text/plain, */*\n\napplication/json;\n\nx-ca-key:${xCaKey}\nx-ca-nonce:${uuid}\n${_url}`;
        const hmac = CryptoJS.HmacSHA256(toEnc, ekey);
        return CryptoJS.enc.Base64.stringify(hmac);
    }
    
  • 只需使用 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 文件,尝试以下操作:

  • ctrl+alt+h:全选
  • ctrl+alt+j:取消全部选择
  • ctrl+alt+k:反选

示例三:实战

需求如下:

  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
  • 13
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值