Skip to content

Commit

Permalink
Merge pull request #145 from obgnail/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
obgnail authored Sep 6, 2023
2 parents 6f6ef20 + c4efea4 commit 064332d
Show file tree
Hide file tree
Showing 13 changed files with 282 additions and 173 deletions.
57 changes: 31 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@

> 目前此方法仅限 windows 平台。
1. [下载](https://github.com/obgnail/typora_plugin/releases/latest)插件源码。
1. [下载](https://github.com/obgnail/typora_plugin/releases/latest) 插件源码。

2. 进入 Typora 安装路径,找到包含 `window.html` 的文件夹 A。(一般是 `Typora/resources/window.html` 或者 `Typora/resources/app/window.html`

Expand All @@ -66,7 +66,7 @@

## 如何使用:方法二(手动)

1. [下载](https://github.com/obgnail/typora_plugin/releases/latest)插件源码。
1. [下载](https://github.com/obgnail/typora_plugin/releases/latest) 插件源码。
2. 进入 Typora 安装路径,找到包含 `window.html` 的文件夹 A。(一般是 `Typora/resources/window.html` 或者 `Typora/resources/app/window.html`,推荐使用 everything 找一下)
3. 打开文件夹 A,将源码的 plugin 文件夹粘贴进该文件夹下。
4. 打开文件 `A/window.html`。搜索文件内容 `<script src="./app/window/frame.js" defer="defer"></script>` 或者 `<script src="./appsrc/window/frame.js" defer="defer"></script>`,并在 **后面** 加入 `<script src="./plugin/index.js" defer="defer"></script>`。保存。
Expand Down Expand Up @@ -478,19 +478,19 @@ const BUILTIN = [

#### 简介

从 Typora Plugin 1.2.1 版本开始,本插件系统提供开放能力,**支持用户在右键菜单中调用的自定义命令**
从 Typora Plugin 1.2.1 版本开始,本插件系统提供开放能力,支持用户 **在右键菜单中调用自定义的命令**

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

- 只需使用 `style = () => {}`,即可注册 css。
- 只需使用 `style = () => "..."`,即可注册 css。
- 只需使用 `hint = () => "将当前标题的路径复制到剪切板"`,即可注册 hint。
- 只需使用 `hotkey = () => ["ctrl+shift+y"]` ,即可注册快捷键。
- 只需使用 `this.modal` 函数即可自动生成自定义的模态框。
- init、selector、html、process、callback 等等生命周期函数
- init、selector、html、process、callback 等等生命周期函数

```js
class fullPathCopy extends BaseCustomPlugin {
style = () => {}
style = () => "..."
hint = () => "将当前标题的路径复制到剪切板"
hotkey = () => ["ctrl+shift+y"]
callback = anchorNode => {
Expand All @@ -517,39 +517,43 @@ class fullPathCopy extends BaseCustomPlugin {

仅需两步:

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



#### 示例

需求如下:

1. 在右键菜单中添加一个 `获取标题路径` 的功能(类似于 `messing9.md\无 一级标题\开放平台(WIP) 二级标题\window_tab 三级标题`
2. 此功能只要在光标位于【正文标题】中才可使用。
3. 为此功能添加快捷键 `ctrl+shift+y`
1. 在右键菜单中添加一个 `获取标题路径` (类似于 `messing9.md\无 一级标题\开放平台(WIP) 二级标题\window_tab 三级标题`的功能。
2. 当光标位于【正文标题】中才可使用。
3. 快捷键 `ctrl+shift+y`

实现:

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

- name:右键菜单中展示的名称
- enable:是否启用此插件
- config:插件自己的配置
- name:(必选)右键菜单中展示的名称
- enable:(必选)是否启用此插件
- config:(可选)插件自己的配置

```toml
# ./plugin/global/settings/custom_plugin.user.toml

[fullPathCopy]
name = "复制标题路径"
enable = true
[fullPathCopy.config]
ignore_empty_header = false
add_space = true
full_file_path = false

[fullPathCopy.config]
ignore_empty_header = false
add_space = true
full_file_path = false
```

步骤二:在 `./plugin/custom/plugins` 目录下,创建和 plugin 参数同名的文件(`fullPathCopy.js`),在此文件中创建一个 class 继承自 BaseCustomPlugin,并导出为 `plugin`
> 如果您对 TOML 不太了解,可以花三分钟了解 [TOML教程](https://toml.io/cn/v1.0.0)
步骤二:在 `./plugin/custom/plugins` 目录下,创建和插件同名的文件(`fullPathCopy.js`),在此文件中创建一个 class 继承自 BaseCustomPlugin,并导出为 `plugin`

```js
// ./plugin/custom/plugins/fullPathCopy.js
Expand Down Expand Up @@ -622,15 +626,16 @@ class fullPathCopy extends BaseCustomPlugin {
// 10
module.exports = { plugin: fullPathCopy };

// 1. 创建同名的 class,继承 BaseCustomPlugin 类。此时,fullPathCopy 将自动拥有 utils 属性 和 info 属性 和 modal 方法。
// - utils:插件系统自带的静态工具类,其定义在 `./plugin/global/core/plugin.js/utils`。其中有个最重要的函数:`utils.getPlugin(fixed_name)` 用于获取已经实现的全部插件,调用其 API。具体的 API 可看 openPlatformAPI.md 文件。
// - info:该插件在 `custom_plugin.user.toml` 里的所有配置。
// - modal:生成自定义的模态框,和用户交互。具体用法可以查看 __modal_example.js
// 2. selector:当用户在哪个位置右键弹出菜单时,出现此命令(空串:任何位置都展示),在这里的含义就是:只在【正文标题】弹出此命令
// 1. 创建 class,继承 BaseCustomPlugin 类。此时,fullPathCopy 将自动拥有 utils、info、config 属性 和 modal 方法。
// - utils:插件系统自带的静态工具类,其定义在 `./plugin/global/core/plugin.js/utils`。其中有两个最重要的函数:utils.getPlugin(fixed_name) 和 utils.getCustomPlugin(fixed_name) 用于获取已经实现的全部插件,调用其 API。具体的 API 可看 openPlatformAPI.md 文件
// - info:该插件在 custom_plugin.user.toml 里的所有字段
// - config:等同于 info.config
// - modal:生成自定义的模态框,和用户交互。具体用法可以参考 __modal_example.js
// 2. selector:当光标位于哪些位置时,此命令才可用(空串:任何位置都可用),在这里的含义就是:只当光标位于【正文标题】时可用
// 3. hint:当鼠标移动到右键菜单时的提示
// 4. init:在这里初始化你要的变量
// 5. style:给 Typora 插入 style 标签。返回值为 `{id: "", text: ""}`。其中 id 为此 style 标签的 id,text 为 style 内容
// 6. html:在这里为 Typora 插入 HTML 文本
// 5. style:给 Typora 插入 style 标签。返回值为 string。若你想指定标签的 id,也可以返回 {textID: "", text: ""}。其中 textID 为此 style 标签的 id,text 为 style 内容
// 6. html:在这里为 Typora 插入 HTML 标签
// 7. hotkey:为 callabck 注册快捷键
// 8. process:在这里添加 listener 和修改 Typora 的第一方函数
// 9. callback:右键菜单中点击/键入快捷键后的回调函数。anchorNode: 鼠标光标所在的 element
Expand Down
11 changes: 7 additions & 4 deletions plugin/commander.js
Original file line number Diff line number Diff line change
Expand Up @@ -274,9 +274,11 @@ class commanderPlugin extends global._basePlugin {
}
}

exec = (cmd, shell, resolve, reject) => {
exec = (cmd, shell, resolve, reject, callback) => {
this.modal.input.value = cmd;
this.modal.shellSelect.value = shell;
this.modal.commit.style.display = "block";

const _shell = this.getShellCommand(shell);
const _cmd = this.replaceArgs(cmd, shell);
this.utils.Package.ChildProcess.exec(
Expand All @@ -293,6 +295,7 @@ class commanderPlugin extends global._basePlugin {
resolve = resolve ? resolve : console.log;
resolve(stdout);
}
callback && callback(err, stdout, stderr);
})
}

Expand All @@ -308,9 +311,9 @@ class commanderPlugin extends global._basePlugin {
this.modal.pre.classList.add("error");
}

silentExec = (cmd, shell) => this.exec(cmd, shell, null, null);
errorExec = (cmd, shell) => this.exec(cmd, shell, null, this.showStdErr);
alwaysExec = (cmd, shell) => this.exec(cmd, shell, this.showStdout, this.showStdErr);
silentExec = (cmd, shell, callback) => this.exec(cmd, shell, null, null, callback);
errorExec = (cmd, shell, callback) => this.exec(cmd, shell, null, this.showStdErr, callback);
alwaysExec = (cmd, shell, callback) => this.exec(cmd, shell, this.showStdout, this.showStdErr, callback);

commit = () => {
const cmd = this.modal.input.value;
Expand Down
75 changes: 42 additions & 33 deletions plugin/custom/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class CustomPlugin extends global._basePlugin {
style = () => this.modalHelper.style()
html = () => this.modalHelper.html()
hotkey = () => this.hotkeyHelper.hotkey()
modal = (customPlugin, modal, callback) => this.modalHelper.modal(customPlugin, modal, callback)
modal = (customPlugin, modal, callback, cancelCallback) => this.modalHelper.modal(customPlugin, modal, callback, cancelCallback)
process = () => this.modalHelper.process()
dynamicCallArgsGenerator = anchorNode => this.dynamicCallHelper.dynamicCallArgsGenerator(anchorNode)
call = name => this.dynamicCallHelper.call(name)
Expand All @@ -21,22 +21,33 @@ class loadPluginHelper {
this.controller = controller;
}

updateUserSetting = (allPlugins) => {
updateUserSetting = allPlugins => {
const toml = "./plugin/global/settings/custom_plugin.user.toml";
const exist = this.controller.utils.existPath(this.controller.utils.joinPath(toml));
if (exist) {
if (this.controller.utils.existInPluginPath(toml)) {
const userSettings = this.controller.utils.readToml(toml);
allPlugins = this.controller.utils.merge(allPlugins, userSettings);
}
return allPlugins
}

insertStyle = (fixed_name, style) => {
if (!style) return;

let textID = style["textID"];
let text = style["text"];
if (typeof style === "string") {
textID = `custom-plugin-${fixed_name}-style`;
text = style;
}
this.controller.utils.insertStyle(textID, text);
}

load() {
let allPlugins = this.controller.utils.readToml("./plugin/global/settings/custom_plugin.default.toml");
allPlugins = this.updateUserSetting(allPlugins);
for (const fix_name of Object.keys(allPlugins)) {
const custom = allPlugins[fix_name];
custom.plugin = fix_name;
for (const fixed_name of Object.keys(allPlugins)) {
const custom = allPlugins[fixed_name];
custom.plugin = fixed_name;

if (!custom.enable) continue

Expand All @@ -47,8 +58,7 @@ class loadPluginHelper {
const instance = new plugin(custom, this.controller);
if (this.check(instance)) {
instance.init();
const style = instance.style();
style && this.controller.utils.insertStyle(style.textID, style.text);
this.insertStyle(fixed_name, instance.style());
instance.html();
instance.process();
this.controller.custom[instance.name] = instance;
Expand Down Expand Up @@ -199,8 +209,6 @@ class modalHelper {
this.utils.insertDiv(modal);
}

hide = () => this.entities.modal.style.display = "none";

process = () => {
this.entities = {
modal: document.getElementById("plugin-custom-modal"),
Expand All @@ -211,24 +219,8 @@ class modalHelper {
cancel: document.querySelector("#plugin-custom-modal button.plugin-modal-cancel"),
}

this.entities.cancel.addEventListener("click", this.hide)

this.entities.submit.addEventListener("click", () => {
const name = this.entities.content.getAttribute("custom-plugin-name");
const plugin = this.custom[name];
if (!plugin) return;

this.pluginModal.components.forEach(component => {
if (!component.label || !component.type || !component.id) return;
const div = this.entities.body.querySelector(`.form-group[component-id="${component.id}"]`);
if (div) {
component.submit = this.getWidgetValue(component.type, div);
}
})
this.callback && this.callback(this.pluginModal.components);
this.hide();
})

this.entities.cancel.addEventListener("click", () => this.onButtonClick(this.cancelCallback))
this.entities.submit.addEventListener("click", () => this.onButtonClick(this.callback))
this.entities.modal.addEventListener("keydown", ev => {
if (ev.key === "Enter") {
this.entities.submit.click();
Expand All @@ -242,6 +234,22 @@ class modalHelper {
}, true)
}

onButtonClick = callback => {
const name = this.entities.content.getAttribute("custom-plugin-name");
const plugin = this.custom[name];
if (!plugin) return;

this.pluginModal.components.forEach(component => {
if (!component.label || !component.type || !component.id) return;
const div = this.entities.body.querySelector(`.form-group[component-id="${component.id}"]`);
if (div) {
component.submit = this.getWidgetValue(component.type, div);
}
})
callback && callback(this.pluginModal.components);
this.entities.modal.style.display = "none";
}

getWidgetValue = (type, widget) => {
switch (type.toLowerCase()) {
case "input":
Expand Down Expand Up @@ -302,10 +310,11 @@ class modalHelper {
}

// modal: {title: "", components: [{name: "", type: "", value: ""}]}
modal = (customPlugin, modal, callback) => {
if (modal && callback instanceof Function) {
modal = (customPlugin, modal, callback, cancelCallback) => {
if (customPlugin && customPlugin["name"] && modal && callback instanceof Function) {
this.pluginModal = modal;
this.callback = callback;
this.cancelCallback = cancelCallback;

this.entities.content.setAttribute("custom-plugin-name", customPlugin.name);
this.entities.title.innerText = modal.title;
Expand All @@ -327,8 +336,8 @@ class BaseCustomPlugin {
this.controller = controller;
}

modal(pluginModal, callback) {
this.controller.modal(this, pluginModal, callback);
modal(pluginModal, callback, cancelCallback) {
this.controller.modal(this, pluginModal, callback, cancelCallback);
}

init = () => {
Expand Down
26 changes: 14 additions & 12 deletions plugin/custom/plugins/__modal_example.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
/* modalExample 类展示了如何使用 this.modal 函数:
直接使用this.modal即可弹出自定义的模态框,待用户点击【确定】后会调用回调函数
this.modal:
1. title(必须): 模态框的标题
2. components: 模态框里的内容,支持类型:
1,input
2. password
3. p
4. textarea
5. checkbox
6. radio
7. select
8. file
3. callback: 回调函数
this.modal args:
1. modal:
- title(必须): 模态框的标题
- components: 模态框里的组件,支持类型:
1,input
2. password
3. p
4. textarea
5. checkbox
6. radio
7. select
8. file
2. onSubmitCallback: 当用户点击【确认】后的回调函数
3. onCancelCallback: 当用户点击【取消】后的回调函数
*/
class modalExample extends BaseCustomPlugin {
selector = () => ""
Expand Down
8 changes: 2 additions & 6 deletions plugin/custom/plugins/kanban.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ class kanbanPlugin extends BaseCustomPlugin {

style = () => {
const maxHeight = (this.config.KANBAN_MAX_HEIGHT < 0) ? "" : `max-height: ${this.config.KANBAN_MAX_HEIGHT}px;`;

let text = `
.plugin-kanban .plugin-kanban-title {
font-size: 1.5rem;
Expand Down Expand Up @@ -78,13 +77,10 @@ class kanbanPlugin extends BaseCustomPlugin {
word-wrap: break-word;
}
`

if (this.utils.isBetaVersion) {
text = `.plugin-kanban { font-family: sans-serif; }
${text}
.md-fences-advanced:not(.md-focus) .CodeMirror { display: none; }`
text = `.plugin-kanban { font-family: sans-serif; } ${text} .md-fences-advanced:not(.md-focus) .CodeMirror { display: none; }`
}
return {textID: "plugin-kanban-style", text: text}
return text
}

init = () => {
Expand Down
Loading

0 comments on commit 064332d

Please sign in to comment.