diff --git a/README-ZH.md b/README-ZH.md index 22c0f75..6cb3903 100644 --- a/README-ZH.md +++ b/README-ZH.md @@ -9,17 +9,23 @@ **Chem** 是一个旨在提升化学相关笔记记录体验的 [Obsidian.md](https://obsidian.md/) 插件。目前,本插件能够将您笔记中的SMILES字符串渲染为化学结构式。(基于 [Smiles Drawer](https://github.com/reymond-group/smilesDrawer) 与 [RDKit.js](https://github.com/rdkit/rdkit-js)) > [!Note] -> 最新插件版本: 0.4.0 -> 文档版本: 0.4.0 +> 最新插件版本: 0.4.1 +> 文档版本: 0.4.1 ## 功能介绍 -### 渲染 SMILES 字符串为化学结构式 +### 将 SMILES 字符串渲染为化学结构式 + +#### 代码块渲染 如图所示,本插件能够识别语言被标记为 `smiles` 的代码块,并将其中的 SMILES 字符串逐行转化为化学结构式。得益于 SMILES 的纯文本特性,数据能够持久保存,该功能将得到稳定支持;此外,由于 SMILES 是一个通用标准,即使本插件换用了其他化学信息学工具包,SMILES 字符串仍能够被正确解析和渲染 ![渲染 SMILES 字符串](https://github.com/Acylation/obsidian-chem/assets/73122375/a9f9a440-dc66-4689-ab1a-1ef265242778) +#### 行内渲染 + +行内渲染功能可以在插件设置页中启用。具有特定前缀的代码行将被 Chem 插件识别、解析。默认的语法格式是 `$smiles=C1=CC=CC=C1`,前缀同样可以在插件设置页中调整 + #### 设置结构图像大小和样式主题 您可以在插件设置页面调整化学结构式的尺寸比例,或指定图像大小,并分别调整浅色模式/深色模式下对应的结构式颜色主题。插件设置发生更改,或浅色/深色模式切换后,图像样式会自动更新 @@ -40,7 +46,7 @@ > 本功能依赖 Dataview 插件本体,若要使用本功能,请确保您已安装并启用 Dataview 插件 > > 运行 DataviewJS 需要调用 `eval()` 函数,具有一定的风险。请确保您输入/粘贴的代码是受信任的 -> +> > 若您希望在启用 Dataview 查询式 (Queries) 时禁用 DataviewJS,请在 Dataview 插件设置中禁用 `Enable JavaScript Queries` 或 `Enable Inline JavaScript Queries` 选项 ## SMILES 介绍 @@ -55,9 +61,9 @@ SMILES 使用 ASCII 字符串表示化学结构的特性与 Obsidian 的纯文 ### 如何生成 SMILES 字符串? - 对于简单结构,您在初步学习 SMILES 知识后即可手动输入。对于复杂结构,您可以使用 ChemDraw,[ChemDrawJS](https://chemdrawdirect.perkinelmer.cloud/js/sample/index.html#),[MarvinJS](https://marvinjs-demo.chemaxon.com/latest/index.html) 或 [Ketcher](https://lifescience.opensource.epam.com/KetcherDemoSA/index.html) 等**结构编辑器**先行绘制,再导出 SMILES 字符串。您可以使用 [Obsidian Ketcher](https://github.com/yuleicul/obsidian-ketcher) 插件在 Obsidian 中直接绘制化学结构,并导出 SMILES 字符串。 +对于简单结构,您在初步学习 SMILES 知识后即可手动输入。对于复杂结构,您可以使用 ChemDraw,[ChemDrawJS](https://chemdrawdirect.perkinelmer.cloud/js/sample/index.html#),[MarvinJS](https://marvinjs-demo.chemaxon.com/latest/index.html) 或 [Ketcher](https://lifescience.opensource.epam.com/KetcherDemoSA/index.html) 等**结构编辑器**先行绘制,再导出 SMILES 字符串。您可以使用 [Obsidian Ketcher](https://github.com/yuleicul/obsidian-ketcher) 插件在 Obsidian 中直接绘制化学结构,并导出 SMILES 字符串。 - 您也可以使用 [Open Babel](https://openbabel.org/),[JOELib](https://sourceforge.net/projects/joelib/) 或 [Chemical Translation Service](https://cts.fiehnlab.ucdavis.edu/) 等**化学语言转换工具**/**化学数据库**,将化合物名称、CAS 号、`*.mol` 文件等转化为 SMILES 格式 +您也可以使用 [Open Babel](https://openbabel.org/),[JOELib](https://sourceforge.net/projects/joelib/) 或 [Chemical Translation Service](https://cts.fiehnlab.ucdavis.edu/) 等**化学语言转换工具**/**化学数据库**,将化合物名称、CAS 号、`*.mol` 文件等转化为 SMILES 格式 ## 安装步骤 @@ -107,7 +113,7 @@ SMILES 使用 ASCII 字符串表示化学结构的特性与 Obsidian 的纯文 ## 致谢 -本插件依赖 [Smiles Drawer](https://github.com/reymond-group/smilesDrawer) 实现 SMILES 字符串的解析以及结构式绘制的功能,导入这个包的例子来源于 [Mathpix](https://github.com/Mathpix/mathpix-markdown-it)。在此向这些杰出的工作表示感谢! +本插件依赖 [Smiles Drawer](https://github.com/reymond-group/smilesDrawer) 和 [RDKit.js](https://github.com/rdkit/rdkit-js) 实现 SMILES 字符串的解析以及结构式绘制的功能。[Mathpix](https://github.com/Mathpix/mathpix-markdown-it) 展示了导入 Smiles Drawer 包以及使用代码块语法的实例。向这些杰出的工作表示感谢! 在整个开发过程中,官方[开发者文档](https://docs.obsidian.md/Plugins/Getting+started/Build+a+plugin)提供了非常详细的指引,感谢 [@marcusolsson](https://github.com/marcusolsson) 的早期非官方文档,以及对官方开发者文档整理工作的领导! diff --git a/README.md b/README.md index 5a6db09..8fe739a 100644 --- a/README.md +++ b/README.md @@ -9,19 +9,25 @@ **Chem** is a plugin for [Obsidian.md](https://obsidian.md/) providing chemistry support. It allows you to insert chemical structures into your notes through code blocks containing SMILES strings (powered by [Smiles Drawer](https://github.com/reymond-group/smilesDrawer) & [RDKit.js](https://github.com/rdkit/rdkit-js)). > [!Note] -> Latest release: 0.4.0 -> Document version: 0.4.0 +> Latest release: 0.4.1 +> Document version: 0.4.1 ## Features & Usage ### Render SMILES Strings as Chemical Structures +#### Render via Code Block + You can use this plugin to render chemical structures from SMILES strings. Just type the SMILES strings in a code block with `smiles` as the language. Each line should contain only one string. The data is stored as plain text, so you won’t lose it. The renderer will always work, even if the plugin changes its cheminfo core. ![Render SMILES strings into structures](https://github.com/Acylation/obsidian-chem/assets/73122375/a9f9a440-dc66-4689-ab1a-1ef265242778) +#### Inline Render + +You can enable inline render feature in plugin settings. Code lines with specified prefix would be recognized. The default syntax is `$smiles=C1=CC=CC=C1` and the prefix is configurable. + #### Global Sizing and Theming You can adjust the structure scale or the image size and configure light/dark themes of the structure images in the plugin’s settings page. The structures in open notes will be automatically updated when the plugin settings or Obsidian color scheme are changed. @@ -57,9 +63,9 @@ Using SMILES strings to represent molecules is easier and more widely supported ### How to Generate SMILES Strings? - For simple structures, you can type them in manually. However, for more complex ones, you may want to use **structure editors**, such as ChemDraw, [ChemDrawJS](https://chemdrawdirect.perkinelmer.cloud/js/sample/index.html#), [MarvinJS](https://marvinjs-demo.chemaxon.com/latest/index.html) and [Ketcher](https://lifescience.opensource.epam.com/KetcherDemoSA/index.html). There's a Ketcher editor integration plugin [Obsidian Ketcher](https://github.com/yuleicul/obsidian-ketcher) available right in Obsidian. +For simple structures, you can type them in manually. However, for more complex ones, you may want to use **structure editors**, such as ChemDraw, [ChemDrawJS](https://chemdrawdirect.perkinelmer.cloud/js/sample/index.html#), [MarvinJS](https://marvinjs-demo.chemaxon.com/latest/index.html) and [Ketcher](https://lifescience.opensource.epam.com/KetcherDemoSA/index.html). There's a Ketcher editor integration plugin [Obsidian Ketcher](https://github.com/yuleicul/obsidian-ketcher) available right in Obsidian. - Also, you can use **translators** like [Open Babel](https://openbabel.org/), [JOELib](https://sourceforge.net/projects/joelib/) and the [Chemical Translation Service](https://cts.fiehnlab.ucdavis.edu/) to convert chemical names, CAS numbers and `*.mol` files into SMILES strings. +Also, you can use **translators** like [Open Babel](https://openbabel.org/), [JOELib](https://sourceforge.net/projects/joelib/) and the [Chemical Translation Service](https://cts.fiehnlab.ucdavis.edu/) to convert chemical names, CAS numbers and `*.mol` files into SMILES strings. ## Installation @@ -105,9 +111,9 @@ Check out the [roadmap](https://github.com/users/Acylation/projects/6) to see wh [Chemical Structure Renderer](https://github.com/xaya1001/obsidian-Chemical-Structure-Renderer) is a similar plugin of `Chem`, which uses [Ketcher](https://github.com/epam/ketcher), [Indigo](https://github.com/epam/Indigo) online service for parsing and rendering, while `Chem` plugin integrates standalone packages and runs locally. -## Acknowledgement +## Credits -The plugin relies on [Smiles Drawer](https://github.com/reymond-group/smilesDrawer) as the parsing and drawing core, and uses [Mathpix](https://github.com/Mathpix/mathpix-markdown-it) as an example on how to integrate the package. Thank you very much! +The plugin relies on [Smiles Drawer](https://github.com/reymond-group/smilesDrawer) and [RDKit.js](https://github.com/rdkit/rdkit-js) as the parsing and drawing cores, and takes [Mathpix](https://github.com/Mathpix/mathpix-markdown-it) as a good example on integrating Smiles Drawer and use codeblock to decorate the note. Thank you very much! During the whole process of development, I found the [developer documentation](https://docs.obsidian.md/Plugins/Getting+started/Build+a+plugin) super helpful. Massive thanks to [@marcusolsson](https://github.com/marcusolsson) for leading this project! diff --git a/docs/CONTRIBUTING-ZH.md b/docs/CONTRIBUTING-ZH.md index e5a651a..d7892e2 100644 --- a/docs/CONTRIBUTING-ZH.md +++ b/docs/CONTRIBUTING-ZH.md @@ -40,7 +40,7 @@ cd [你的工作文件夹] ``` > [!Note] -> 建议使用一个开发 vault 的配置文件夹作为本插件的工作文件夹。例如 `.obsidian\plugins\)`. +> 建议使用一个开发 vault 的配置文件夹作为本插件的工作文件夹。例如 `.obsidian\plugins\`. ```cmd git clone https://github.com/Acylation/obsidian-chem.git chem diff --git a/package-lock.json b/package-lock.json index 2a6a518..91a2e62 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1786,9 +1786,9 @@ } }, "node_modules/@types/codemirror": { - "version": "0.0.108", - "resolved": "https://registry.npmmirror.com/@types/codemirror/-/codemirror-0.0.108.tgz", - "integrity": "sha512-3FGFcus0P7C2UOGCNUVENqObEb4SFk+S8Dnxq7K6aIsLVs/vDtlangl3PEO0ykaKXyK56swVF6Nho7VsA44uhw==", + "version": "5.60.8", + "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.8.tgz", + "integrity": "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==", "dev": true, "dependencies": { "@types/tern": "*" @@ -5348,12 +5348,12 @@ } }, "node_modules/obsidian": { - "version": "1.2.5", - "resolved": "https://registry.npmmirror.com/obsidian/-/obsidian-1.2.5.tgz", - "integrity": "sha512-RKN4W3PaHsoWwlNRg1SV+iJssQ5vnQYzsCSfmFAUHvA8Q8nzk4pW3HGWXSvor3ZM532KECljG86lEx02OvBwpA==", + "version": "1.5.7-1", + "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.5.7-1.tgz", + "integrity": "sha512-T5ZRuQ1FnfXqEoakTTHVDYvzUEEoT8zSPnQCW31PVgYwG4D4tZCQfKHN2hTz1ifnCe8upvwa6mBTAP2WUA5Vng==", "dev": true, "dependencies": { - "@types/codemirror": "0.0.108", + "@types/codemirror": "5.60.8", "moment": "2.29.4" }, "peerDependencies": { @@ -5391,18 +5391,9 @@ "obsidian-daily-notes-interface": "dist/main.js" } }, - "node_modules/obsidian-daily-notes-interface/node_modules/@types/codemirror": { - "version": "5.60.8", - "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.8.tgz", - "integrity": "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==", - "dev": true, - "dependencies": { - "@types/tern": "*" - } - }, "node_modules/obsidian-daily-notes-interface/node_modules/obsidian": { - "version": "1.5.1", - "resolved": "git+ssh://git@github.com/obsidianmd/obsidian-api.git#bbb696aeb8bf5126bf2ecbf84fc8284fe133bc20", + "version": "1.5.7", + "resolved": "git+ssh://git@github.com/obsidianmd/obsidian-api.git#8b2eda0f24285636c8aa116972643e5233a23dc1", "dev": true, "license": "MIT", "dependencies": { @@ -8519,9 +8510,9 @@ } }, "@types/codemirror": { - "version": "0.0.108", - "resolved": "https://registry.npmmirror.com/@types/codemirror/-/codemirror-0.0.108.tgz", - "integrity": "sha512-3FGFcus0P7C2UOGCNUVENqObEb4SFk+S8Dnxq7K6aIsLVs/vDtlangl3PEO0ykaKXyK56swVF6Nho7VsA44uhw==", + "version": "5.60.8", + "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.8.tgz", + "integrity": "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==", "dev": true, "requires": { "@types/tern": "*" @@ -11273,12 +11264,12 @@ } }, "obsidian": { - "version": "1.2.5", - "resolved": "https://registry.npmmirror.com/obsidian/-/obsidian-1.2.5.tgz", - "integrity": "sha512-RKN4W3PaHsoWwlNRg1SV+iJssQ5vnQYzsCSfmFAUHvA8Q8nzk4pW3HGWXSvor3ZM532KECljG86lEx02OvBwpA==", + "version": "1.5.7-1", + "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.5.7-1.tgz", + "integrity": "sha512-T5ZRuQ1FnfXqEoakTTHVDYvzUEEoT8zSPnQCW31PVgYwG4D4tZCQfKHN2hTz1ifnCe8upvwa6mBTAP2WUA5Vng==", "dev": true, "requires": { - "@types/codemirror": "0.0.108", + "@types/codemirror": "5.60.8", "moment": "2.29.4" } }, @@ -11311,17 +11302,8 @@ "tslib": "2.1.0" }, "dependencies": { - "@types/codemirror": { - "version": "5.60.8", - "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.8.tgz", - "integrity": "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==", - "dev": true, - "requires": { - "@types/tern": "*" - } - }, "obsidian": { - "version": "git+ssh://git@github.com/obsidianmd/obsidian-api.git#bbb696aeb8bf5126bf2ecbf84fc8284fe133bc20", + "version": "git+ssh://git@github.com/obsidianmd/obsidian-api.git#8b2eda0f24285636c8aa116972643e5233a23dc1", "dev": true, "from": "obsidian@github:obsidianmd/obsidian-api#master", "requires": { diff --git a/src/global/chemCore.ts b/src/global/chemCore.ts index f910d0e..19eba26 100644 --- a/src/global/chemCore.ts +++ b/src/global/chemCore.ts @@ -6,14 +6,33 @@ import RDKitCore from '../lib/core/rdkitCore'; export let gRenderCore: ChemCore; -export const setCore = async (settings: ChemPluginSettings) => { +export const setCore = async ( + settings: ChemPluginSettings, + onFallback: (error: string) => void +) => { if (!gRenderCore || settings.core !== gRenderCore.id) { - if (settings.core == 'smiles-drawer') + if (settings.core === 'smiles-drawer') { gRenderCore = new SmilesDrawerCore(settings); - if (settings.core == 'rdkit') { - gRenderCore = await RDKitCore.init(settings); + updateCoreSettings(settings); + } else if (settings.core === 'rdkit') { + try { + gRenderCore = await RDKitCore.init(settings); + updateCoreSettings(settings); + } catch (error) { + onFallback(error); + } + } else { + onFallback(`invalid chem core id. ${settings.core}`); } } +}; + +export const setFallbackCore = async (settings: ChemPluginSettings) => { + gRenderCore = new SmilesDrawerCore(settings); + updateCoreSettings(settings); +}; + +export const updateCoreSettings = (settings: ChemPluginSettings) => { gRenderCore.settings = settings; }; diff --git a/src/lib/core/coreFallbackModal.ts b/src/lib/core/coreFallbackModal.ts new file mode 100644 index 0000000..55f37f0 --- /dev/null +++ b/src/lib/core/coreFallbackModal.ts @@ -0,0 +1,50 @@ +import { Modal, App, ButtonComponent } from 'obsidian'; +import { i18n } from 'src/lib/i18n'; + +export class CoreFallbackModal extends Modal { + msg: string; + onConfirm: () => void; + + constructor(app: App, text: string, onConfrim: () => void) { + super(app); + this.msg = text; + this.onConfirm = onConfrim; + } + + onOpen() { + const { contentEl } = this; + + contentEl.createEl('h3', { + text: i18n.t('modals.core-fallback.title'), + }); + + contentEl.createEl('div', { + text: i18n.t('modals.core-fallback.message'), + }); + + contentEl.createEl('br'); + + contentEl.createEl('div', { + text: this.msg, + }); + + const div = contentEl.createDiv(); + div.style.display = 'flex'; + div.style.flex = '1 1 auto'; + div.style.justifyContent = 'flex-end'; + div.style.padding = '3px'; + + new ButtonComponent(div) + .setCta() + .setButtonText(i18n.t('modals.core-fallback.confirm')) + .onClick(() => { + this.close(); + }); + } + + onClose() { + this.onConfirm(); + const { contentEl } = this; + contentEl.empty(); + } +} diff --git a/src/lib/core/rdkitCore.ts b/src/lib/core/rdkitCore.ts index c077b94..a034eba 100644 --- a/src/lib/core/rdkitCore.ts +++ b/src/lib/core/rdkitCore.ts @@ -8,7 +8,7 @@ import { getCurrentTheme } from 'src/lib/themes/getCurrentTheme'; import { normalizePath, requestUrl, Notice } from 'obsidian'; import { i18n } from '../i18n'; -import * as path from 'path'; +import { githubAsset } from 'typings/githubAsset'; export default class RDKitCore implements ChemCore { id: 'rdkit'; @@ -24,7 +24,19 @@ export default class RDKitCore implements ChemCore { } static async init(settings: ChemPluginSettings) { - if (!window.RDKit) window.RDKit = await loadRDKit(); + if (!window.RDKit) { + try { + window.RDKit = await loadRDKit(); + } catch (e) { + try { + window.RDKit = await loadRDKitUnpkg(); + } catch (e) { + throw Error( + "Initializing rdkit failed: Can't fetch resources from unpkg." + ); + } + } + } return new RDKitCore(settings, window.RDKit); } @@ -39,7 +51,7 @@ export default class RDKitCore implements ChemCore { if (!rxn) return this.logError(source); svgstr = await this.drawReaction(rxn); } else { - const mol = this.core.get_mol(source); + const mol = this.core.get_mol(source, JSON.stringify({})); if (!mol) return this.logError(source); // https://greglandrum.github.io/rdkit-blog/posts/2024-01-11-using-abbreviations.html @@ -119,9 +131,13 @@ export default class RDKitCore implements ChemCore { private logError = (source: string) => { const div = createDiv(); - div.createDiv().setText(i18n.t('errors.source.title', { source })); + div.createDiv('error-source').setText( + i18n.t('errors.source.title', { source }) + ); div.createEl('br'); div.createDiv().setText(i18n.t('errors.rdkit.title')); + div.style.wordBreak = `break-word`; + div.style.userSelect = `text`; return div; }; } @@ -130,24 +146,22 @@ export default class RDKitCore implements ChemCore { // Initialize reference: https://github.com/rdkit/rdkit-js/tree/master/typescript const loadRDKit = async () => { const assetPath = normalizePath( - path.join(app.vault.configDir, 'plugins', 'chem', 'rdkit') + [app.vault.configDir, 'plugins', 'chem', 'rdkit'].join('/') ); if (!(await app.vault.adapter.exists(assetPath))) { - console.log(assetPath); await app.vault.adapter.mkdir(assetPath); } - const jsPath = path.join(assetPath, 'RDKit_minimal.js'); + const jsPath = [assetPath, 'RDKit_minimal.js'].join('/'); await checkOrDownload('RDKit_minimal.js'); - const wasmPath = path.join(assetPath, 'RDKit_minimal.wasm'); + const wasmPath = [assetPath, 'RDKit_minimal.wasm'].join('/'); await checkOrDownload('RDKit_minimal.wasm'); const rdkitBundler = document.body.createEl('script'); rdkitBundler.type = 'text/javascript'; rdkitBundler.id = 'chem-rdkit-bundler'; rdkitBundler.innerHTML = await app.vault.adapter.read(jsPath); - // backup rdkitBundler.src = 'https://unpkg.com/@rdkit/rdkit/dist/RDKit_minimal.js' const getWasmURL = async () => URL.createObjectURL( @@ -155,7 +169,7 @@ const loadRDKit = async () => { type: 'application/wasm', }) ); - const url = await getWasmURL(); //backup: https://unpkg.com/@rdkit/rdkit/dist/RDKit_minimal.wasm + const url = await getWasmURL(); const RDKit = await window.initRDKitModule({ locateFile: () => url, }); @@ -163,32 +177,42 @@ const loadRDKit = async () => { return RDKit; }; -const fetchAsset = async (target: string, localPath: string) => { - let res; - let data; +// See https://github.com/rdkit/rdkit-js/issues/160 +const loadRDKitUnpkg = async () => { + const rdkitBundler = document.body.createEl('script'); + new Notice('Fetching RDKit.js from unpkg...'); + + rdkitBundler.innerHTML = await requestUrl( + 'https://unpkg.com/@rdkit/rdkit/dist/RDKit_minimal.js' + ).text; + + const RDKit = await window.initRDKitModule({ + locateFile: () => + 'https://unpkg.com/@rdkit/rdkit/dist/RDKit_minimal.wasm', + }); + new Notice('RDKit.js has been successfully loaded.'); + return RDKit; +}; - res = requestUrl( +const fetchAsset = async (target: string, localPath: string) => { + const assetInfo = await requestUrl( `https://api.github.com/repos/acylation/obsidian-chem/releases/tags/${ app.plugins.getPlugin('chem')?.manifest.version ?? '0.4.0' }` - ); - data = await res.json; - const asset = data.assets.find((v: any) => v.name == target); - if (asset == undefined) { - throw 'Could not find the online asset!'; - } - res = requestUrl({ + ).json; + const asset = assetInfo.assets.find((a: githubAsset) => a.name == target); + if (asset === undefined) throw Error('Could not find the online asset!'); + + const data = await requestUrl({ url: asset.url, headers: { Accept: 'application/octet-stream' }, - }); - data = await res.arrayBuffer; + }).arrayBuffer; await app.vault.adapter.writeBinary(localPath, data); }; -// TODO: i18n const checkOrDownload = async (target: string) => { const assetPath = normalizePath( - path.join(app.vault.configDir, 'plugins', 'chem', 'rdkit', target) + [app.vault.configDir, 'plugins', 'chem', 'rdkit', target].join('/') ); if (!(await app.vault.adapter.exists(assetPath))) { @@ -196,11 +220,14 @@ const checkOrDownload = async (target: string) => { try { await fetchAsset(target, assetPath); new Notice( - `Chem: Resource ${target} successfully downloaded!`, + `Chem: Resource ${target} successfully downloaded! ✔️`, 5000 ); } catch (error) { - new Notice(`Chem: Failed to fetch ${target}: ` + error); + new Notice(`Chem: Failed to fetch ${target}: ${error} ❌`); + throw Error( + `Failed to fetch resource ${target} from GitHub release.` + ); } } }; diff --git a/src/lib/translations/en.json b/src/lib/translations/en.json index 8a49c64..1929f7a 100644 --- a/src/lib/translations/en.json +++ b/src/lib/translations/en.json @@ -101,6 +101,13 @@ "name": "Inline SMILES Prefix", "description": "The prefix to inline SMILES." } + }, + "modals": { + "core-fallback": { + "title": "Switch core failed", + "confirm": "Confirm", + "message": "Failed to switch to the target package, and will fallback to the default Smiles Drawer core." + } } } } diff --git a/src/lib/translations/zh-CN.json b/src/lib/translations/zh-CN.json index 3bc8aa3..93c120f 100644 --- a/src/lib/translations/zh-CN.json +++ b/src/lib/translations/zh-CN.json @@ -102,6 +102,13 @@ "description": "行内 SMILES 的前缀。" } } + }, + "modals": { + "core-fallback": { + "title": "切换渲染器失败", + "confirm": "确认", + "message": "切换目标渲染器失败,将回退为默认渲染器 Smiles Drawer。" + } } } } \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index b8bf56c..6c47e15 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,10 +5,11 @@ import { ChemSettingTab } from './settings/SettingTab'; import { SmilesBlock } from './SmilesBlock'; import { inlinePlugin } from './SmilesInline'; -import { setCore, clearCore } from './global/chemCore'; +import { setCore, clearCore, setFallbackCore } from './global/chemCore'; import { setBlocks, clearBlocks } from './global/blocks'; import { getDataview, clearDataview } from './global/dataview'; import { setObserver, detachObserver } from './lib/themes/themeObserver'; +import { CoreFallbackModal } from './lib/core/coreFallbackModal'; export default class ChemPlugin extends Plugin { settings: ChemPluginSettings; @@ -19,7 +20,13 @@ export default class ChemPlugin extends Plugin { // initialize global variables setBlocks(); setObserver(); - await setCore(this.settings); + setCore(this.settings, (error: string) => { + new CoreFallbackModal(this.app, error, async () => { + this.settings.core = 'smiles-drawer'; + await this.saveSettings(); + await setFallbackCore(this.settings); + }).open(); + }); this.addSettingTab(new ChemSettingTab({ app: this.app, plugin: this })); diff --git a/src/settings/SettingTab.ts b/src/settings/SettingTab.ts index 6b62742..fa7c6d6 100644 --- a/src/settings/SettingTab.ts +++ b/src/settings/SettingTab.ts @@ -1,21 +1,18 @@ import ChemPlugin from '../main'; import { DEFAULT_SETTINGS } from './base'; import { themeList } from '../lib/themes/theme'; - -import { setCore } from 'src/global/chemCore'; +import { + setCore, + updateCoreSettings, + setFallbackCore, +} from 'src/global/chemCore'; import { refreshBlocks } from 'src/global/blocks'; import { clearDataview, getDataview } from 'src/global/dataview'; -import { LivePreview } from './LivePreview'; - -import { - App, - Platform, - PluginSettingTab, - Setting, - SliderComponent, -} from 'obsidian'; +import { App, PluginSettingTab, Setting, SliderComponent } from 'obsidian'; import { i18n } from 'src/lib/i18n'; +import { LivePreview } from './LivePreview'; +import { CoreFallbackModal } from '../lib/core/coreFallbackModal'; export class ChemSettingTab extends PluginSettingTab { plugin: ChemPlugin; @@ -134,7 +131,6 @@ export class ChemSettingTab extends PluginSettingTab { new Setting(containerEl) .setName(i18n.t('settings.advanced.core.name')) .setDesc(i18n.t('settings.advanced.core.description')) - .setDisabled(Platform.isIosApp) .addDropdown((dropdown) => dropdown .addOptions({ @@ -145,7 +141,18 @@ export class ChemSettingTab extends PluginSettingTab { .onChange(async (value: 'rdkit' | 'smiles-drawer') => { this.plugin.settings.core = value; await this.plugin.saveSettings(); - await this.updateCore(); + await setCore( + this.plugin.settings, + async (error: string) => { + new CoreFallbackModal(app, error, async () => { + dropdown.setValue('smiles-drawer'); + this.plugin.settings.core = 'smiles-drawer'; + await this.plugin.saveSettings(); + setFallbackCore(this.plugin.settings); + onSettingsChange(); + }).open(); + } + ); onSettingsChange(); }) ); @@ -379,5 +386,5 @@ export class ChemSettingTab extends PluginSettingTab { refreshBlocks(); } - updateCore = async () => await setCore(this.plugin.settings); + updateCore = () => updateCoreSettings(this.plugin.settings); } diff --git a/typings/githubAsset.d.ts b/typings/githubAsset.d.ts new file mode 100644 index 0000000..b0f3428 --- /dev/null +++ b/typings/githubAsset.d.ts @@ -0,0 +1,36 @@ +export interface githubAsset { + url: string; + id: number; + node_id: string; + name: string; + label?: null; + uploader: Uploader; + content_type: string; + state: string; + size: number; + download_count: number; + created_at: string; + updated_at: string; + browser_download_url: string; +} + +interface Uploader { + login: string; + id: number; + node_id: string; + avatar_url: string; + gravatar_id: string; + url: string; + html_url: string; + followers_url: string; + following_url: string; + gists_url: string; + starred_url: string; + subscriptions_url: string; + organizations_url: string; + repos_url: string; + events_url: string; + received_events_url: string; + type: string; + site_admin: boolean; +}