├── .editorconfig ├── .gitattributes ├── .github └── workflows │ └── release.yml ├── .gitignore ├── .vscode ├── settings.json └── tasks.json ├── CHANGELOG.md ├── DEV_NOTES.MD ├── LICENSE ├── README.md ├── biome.json ├── esbuild.config.mjs ├── manifest.json ├── package.json ├── preview.png ├── src ├── HelpMateAPI.ts ├── UI │ ├── browsers │ │ ├── EmbeddedBrowser.tsx │ │ ├── IFrameBrowser.tsx │ │ └── WebViewBrowser.tsx │ ├── codeblock.tsx │ ├── settingsTab │ │ ├── SettingsTab.tsx │ │ └── integrationWithSettings.ts │ └── sidepane │ │ ├── AcknowledgeWebUse.tsx │ │ ├── HelpMateView.tsx │ │ ├── HelpMateViewContainer.tsx │ │ ├── HelpMoreButton.tsx │ │ └── HelpSourceButton.tsx ├── commands.ts ├── main.ts ├── resources.ts ├── settings.ts └── types.d.ts ├── styles.css ├── tsconfig.json ├── tsdoc.json ├── version-bump.mjs ├── version-github-action.mjs └── versions.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig: https://EditorConfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [*.yml] 14 | indent_style = space 15 | indent_size = 2 16 | 17 | [*.md] 18 | max_line_length = 90 19 | indent_style = space 20 | indent_size = 2 21 | end_of_line = lf 22 | insert_final_newline = true 23 | trim_trailing_whitespace = true 24 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release Obsidian plugin 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - name: Use Node.js 16 | uses: actions/setup-node@v3 17 | with: 18 | node-version: '21.x' 19 | 20 | - name: Build plugin 21 | run: | 22 | npm install 23 | npm run build 24 | 25 | - name: Create release 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | run: | 29 | tag="${GITHUB_REF#refs/tags/}" 30 | 31 | gh release create "$tag" \ 32 | --title="$tag" \ 33 | build/main.js manifest.json styles.css 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # vscode 2 | @updates.md 3 | 4 | # npm 5 | node_modules 6 | package-lock.json 7 | 8 | # build 9 | build 10 | *.js.map 11 | 12 | # other 13 | **/.DS_Store -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave":{ 3 | "source.organizeImports.biome": "explicit" 4 | } 5 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "npm", 6 | "script": "dev", 7 | "problemMatcher": [], 8 | "label": "npm: start", 9 | "detail": "", 10 | "group": { 11 | "kind": "build", 12 | "isDefault": true 13 | } 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.0.7 2 | 3 | ### Updates 4 | - Updating plugin to newest Obsidian recommendations https://docs.obsidian.md/oo24/plugin. 5 | - Transition to Biome from EsLint and Prettier. 6 | 7 | 8 | # 1.0.6 9 | 10 | ## Fixes 11 | 12 | - chores: update dependencies 13 | 14 | # 1.0.5 15 | 16 | ## Fixes 17 | 18 | - Resolved an issue on mobile devices reporting "Invalid URL" for custom sites defined in settings. 19 | - Added a notice in settings for custom sites that some sites won't work on Obsidian Mobile, even though they work on Destkop 20 | 21 | # 1.0.4 22 | 23 | ## New 24 | 25 | - If manually typing a URL into the HelpMate web browser, the URL will be validated before attempting to load it. Thank you [Dharam](https://twitter.com/DharamKapila) for the suggestion. 26 | - Documentation updated to describe the HelpMate feature available in the Community plugins list in Settings. 27 | 28 | ## Fixes 29 | 30 | - Opening settings for the plugin from the HelpMate web browser was broken 31 | -------------------------------------------------------------------------------- /DEV_NOTES.MD: -------------------------------------------------------------------------------- 1 | # Updating the version 2 | 3 | 1. update pacakage.json version number 4 | 2. npm run version (updates the manifest and version file) 5 | 3. commit repo 6 | 4. npm run githubaction (commits the version number tag to the repo and pushes it, which kicks of the github action to prepare the release) 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 TfTHacker https://tfthacker.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HelpMate 2 | 3 | A plugin that adds a sidepane to Obsidian to view help sites for the plugins and themes you have installed. 4 | 5 | ![preview](preview.png) 6 | 7 | # More Information 8 | 9 | https://tfthacker.com/HelpMate 10 | 11 | # Use of Internet 12 | 13 | This plugin is an embedded web browser. It will connect to the internet to retrieve the help sites for the plugins and themes you have installed. It will not connect to the internet for any other reason. It requires a live internet connection to function. 14 | 15 | 16 | 17 | You might also be interested in a few products I have made for Obsidian: 18 | 19 | - [JournalCraft](https://tfthacker.com/jco) - A curated collection of 10 powerful journaling templates designed to enhance your journaling experience. Whether new to journaling or looking to step up your game, JournalCraft has something for you. 20 | - [Cornell Notes Learning Vault](https://tfthacker.com/cornell-notes) - This vault teaches you how to use the Cornell Note-Taking System in your Obsidian vault. It includes learning material, samples, and Obsidian configuration files to enable Cornell Notes in your vault. -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "vcs": { 4 | "enabled": false, 5 | "clientKind": "git", 6 | "useIgnoreFile": false 7 | }, 8 | "files": { 9 | "ignoreUnknown": false, 10 | "ignore": [] 11 | }, 12 | "formatter": { 13 | "enabled": true, 14 | "indentStyle": "tab" 15 | }, 16 | "organizeImports": { 17 | "enabled": true 18 | }, 19 | "linter": { 20 | "enabled": true, 21 | "rules": { 22 | "recommended": true 23 | } 24 | }, 25 | "javascript": { 26 | "formatter": { 27 | "quoteStyle": "double" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /esbuild.config.mjs: -------------------------------------------------------------------------------- 1 | import console from 'console'; 2 | import fs from 'fs'; 3 | import builtins from 'builtin-modules'; 4 | import esbuild from 'esbuild'; 5 | import process from 'process'; 6 | 7 | 8 | // Ensure build directory exists 9 | if (!fs.existsSync("build")) { 10 | fs.mkdirSync("build"); 11 | } 12 | 13 | // Check and copy files 14 | if (fs.existsSync("manifest.json")) { 15 | fs.copyFile("manifest.json", "build/manifest.json", (err) => { 16 | if (err) console.log("Error copying manifest.json:", err); 17 | }); 18 | } else { 19 | console.log("manifest.json does not exist"); 20 | } 21 | 22 | if (fs.existsSync("styles.css")) { 23 | fs.copyFile("styles.css", "build/styles.css", (err) => { 24 | if (err) console.log("Error copying styles.css:", err); 25 | }); 26 | } else { 27 | console.log("styles.css does not exist"); 28 | } 29 | 30 | 31 | const prod = process.argv[2] === 'production'; 32 | 33 | const context = await esbuild.context({ 34 | entryPoints: ['src/main.ts'], 35 | bundle: true, 36 | minify: prod, 37 | external: [ 38 | 'obsidian', 39 | 'electron', 40 | '@codemirror/autocomplete', 41 | '@codemirror/collab', 42 | '@codemirror/commands', 43 | '@codemirror/language', 44 | '@codemirror/lint', 45 | '@codemirror/search', 46 | '@codemirror/state', 47 | '@codemirror/view', 48 | '@lezer/common', 49 | '@lezer/highlight', 50 | '@lezer/lr', 51 | ...builtins, 52 | ], 53 | format: 'cjs', 54 | target: 'es2018', 55 | logLevel: 'info', 56 | loader: { '.ts': 'ts', '.tsx': 'tsx' }, 57 | jsxFactory: 'h', 58 | jsxFragment: 'Fragment', 59 | sourcemap: prod ? false : 'inline', 60 | treeShaking: true, 61 | outfile: 'build/main.js', 62 | }); 63 | 64 | 65 | if (prod) { 66 | await context.rebuild(); 67 | process.exit(0); 68 | } else { 69 | await context.watch(); 70 | } 71 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "helpmate", 3 | "name": "HelpMate", 4 | "version": "1.0.11", 5 | "minAppVersion": "1.7.2", 6 | "description": "Integrating help systems into the Obsidian UI.", 7 | "author": "TfTHacker", 8 | "authorUrl": "https://github.com/TfTHacker/", 9 | "helpUrl": "https://tfthacker.com/HelpMate", 10 | "isDesktopOnly": false, 11 | "fundingUrl": { 12 | "Support my work": "https://tfthacker.com/sponsor" 13 | } 14 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "helpmate", 3 | "version": "1.0.11", 4 | "description": "HelpMate - integrating help into Obsidian", 5 | "main": "main.js", 6 | "scripts": { 7 | "dev": "node --no-warnings esbuild.config.mjs", 8 | "build": "node --no-warnings esbuild.config.mjs production", 9 | "lint": "biome check ./src", 10 | "version": "node version-bump.mjs", 11 | "githubaction": "node version-github-action.mjs" 12 | }, 13 | "author": "TfT Hacker", 14 | "license": "MIT", 15 | "repository": { 16 | "type": "git", 17 | "url": "git://github.com/TfTHacker/HelpMate.git" 18 | }, 19 | "devDependencies": { 20 | "@biomejs/biome": "1.9.4", 21 | "@types/node": "^22.9.0", 22 | "@types/obsidian-typings": "github:Fevol/obsidian-typings", 23 | "builtin-modules": "4.0.0", 24 | "esbuild": "0.24.0", 25 | "obsidian": "1.7.2", 26 | "ts-node": "^10.9.2", 27 | "tslib": "2.8.1", 28 | "typedoc": "^0.26.11", 29 | "typescript": "^5.6.3" 30 | }, 31 | "dependencies": { 32 | "preact": "^10.24.3" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TfTHacker/obsidian42-HelpMate/fbbc41603f7aeca5442c845cd236ced338dbf311/preview.png -------------------------------------------------------------------------------- /src/HelpMateAPI.ts: -------------------------------------------------------------------------------- 1 | import type HelpMatePlugin from "./main"; 2 | import type { HelpForPlugin } from "./resources"; 3 | import { getPluginHelpList } from "./resources"; 4 | import type { HelpMateSettings } from "./settings"; 5 | 6 | export enum BrowserMode { 7 | webView = "WebView", 8 | iFrame = "IFrame", 9 | } 10 | 11 | /** 12 | * Provide a simple API for use with Templater, Dataview and debugging the complexities of various pages. 13 | * main.ts will attach this to window.helpMateAPI 14 | */ 15 | export default class HelpMateAPI { 16 | private plugin: HelpMatePlugin; 17 | settings: HelpMateSettings; 18 | enableDebugging = { 19 | webViewBrowser: false, 20 | iFrameBrowser: true, 21 | }; 22 | 23 | constructor(plugin: HelpMatePlugin) { 24 | this.plugin = plugin; 25 | this.settings = plugin.settings; 26 | } 27 | 28 | log = (logDescription: string, ...outputs: unknown[]): void => { 29 | console.log(`HelpMate: ${logDescription}`, outputs); 30 | }; 31 | 32 | activateSidePane = async (url?: string): Promise => { 33 | url 34 | ? await this.plugin.activateView(url) 35 | : await this.plugin.activateView(); 36 | }; 37 | 38 | getPluginHelpList = (): HelpForPlugin[] => { 39 | return getPluginHelpList(this.plugin); 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /src/UI/browsers/EmbeddedBrowser.tsx: -------------------------------------------------------------------------------- 1 | // This is the common entry point for the "Browser". It will decide which browser to use based on the environment. 2 | // if it is an electron app then it will use the WebViewBrowser, otherwise it will use the IFrameBrowser. 3 | 4 | import { Platform } from "obsidian"; 5 | import type { FunctionalComponent } from "preact"; 6 | import { useState } from "preact/hooks"; 7 | import { BrowserMode } from "src/HelpMateAPI"; 8 | import type HelpMatePlugin from "src/main"; 9 | import AcknowledgeWebUse from "../sidepane/AcknowledgeWebUse"; 10 | import IFrameBrowser from "./IFrameBrowser"; 11 | import WebViewBrowser from "./WebViewBrowser"; 12 | 13 | interface EmbeddedBrowserProps { 14 | urlAddress: string; 15 | plugin: HelpMatePlugin; 16 | showToolbar?: boolean; 17 | } 18 | 19 | const EmbeddedBrowser: FunctionalComponent = ({ 20 | urlAddress, 21 | plugin, 22 | showToolbar = true, 23 | }: EmbeddedBrowserProps) => { 24 | const browseMode: BrowserMode = plugin.settings.forceIframe 25 | ? BrowserMode.iFrame 26 | : Platform.isDesktop 27 | ? BrowserMode.webView 28 | : BrowserMode.iFrame; 29 | 30 | const [isAcknowledged, setIsAcknowledged] = useState( 31 | plugin.settings.acknowledgedWebUse, 32 | ); 33 | 34 | if (!isAcknowledged) { 35 | return ( 36 | 37 | ); 38 | } 39 | return browseMode === BrowserMode.webView ? ( 40 | 45 | ) : ( 46 | 51 | ); 52 | }; 53 | 54 | export default EmbeddedBrowser; 55 | -------------------------------------------------------------------------------- /src/UI/browsers/IFrameBrowser.tsx: -------------------------------------------------------------------------------- 1 | import { Notice } from "obsidian"; 2 | import { useRef, useState } from "preact/hooks"; 3 | import type HelpMateAPI from "src/HelpMateAPI"; 4 | import type HelpMatePlugin from "src/main"; 5 | import { isValidUrl } from "src/resources"; 6 | import HelpMoreButton from "../sidepane/HelpMoreButton"; 7 | import HelpSourceButton from "../sidepane/HelpSourceButton"; 8 | 9 | interface IFrameBrowserProps { 10 | urlAddress: string; 11 | plugin: HelpMatePlugin; 12 | showToolbar?: boolean; 13 | } 14 | 15 | const IFrameBrowser = ({ 16 | urlAddress, 17 | plugin, 18 | showToolbar = true, 19 | }: IFrameBrowserProps) => { 20 | const api: HelpMateAPI = plugin.HELPMATE_API; 21 | const debug = api.enableDebugging.iFrameBrowser; 22 | const iframeRef = useRef(null); 23 | const [inputUrl, setInputUrl] = useState(urlAddress || ""); 24 | const [iframeUrl, setIframeUrl] = useState(urlAddress || ""); 25 | 26 | const updateUrl = (url: string) => { 27 | let newUrl = url.trim(); 28 | if (!newUrl.startsWith("http://") && !newUrl.startsWith("https://")) { 29 | newUrl = `https://${url}`; 30 | } 31 | if (isValidUrl(newUrl)) { 32 | setInputUrl(newUrl); 33 | setIframeUrl(newUrl); 34 | } else { 35 | new Notice(`Invalid URL ${newUrl}`); 36 | } 37 | }; 38 | 39 | const navigateTo = () => { 40 | if (iframeRef.current) { 41 | debug && api.log("IFrameBrowser: navigateTo", inputUrl); 42 | updateUrl(inputUrl); 43 | } 44 | }; 45 | 46 | const handleKeyPress = (e: KeyboardEvent) => { 47 | if (e.key === "Enter") { 48 | const newUrl = (e.target as HTMLInputElement).value; 49 | updateUrl(newUrl); 50 | } 51 | }; 52 | 53 | return ( 54 |
55 | {showToolbar && ( 56 |
57 | 58 | { 64 | setInputUrl((e.target as HTMLInputElement).value); 65 | }} 66 | placeholder="Enter URL" 67 | /> 68 | 92 | 93 |
94 | )} 95 |