├── .github
└── workflows
│ └── release-please.yml
├── .gitignore
├── .release-please-manifest.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── assets
└── ui.png
├── esbuild.config.mjs
├── manifest.json
├── package-lock.json
├── package.json
├── publish
└── gif
│ ├── settings-search-enter.gif
│ ├── settings-search-keyboard.gif
│ ├── settings-search-settings-search-highlight.gif
│ └── settings-search-up-down.gif
├── release-please-config.json
├── src
├── assets
│ └── main.css
├── main.ts
└── styles.css
├── svelte.config.js
├── tsconfig.json
└── versions.json
/.github/workflows/release-please.yml:
--------------------------------------------------------------------------------
1 | on:
2 | workflow_dispatch:
3 |
4 | push:
5 | branches:
6 | - "main"
7 | env:
8 | PLUGIN_NAME: settings-search
9 |
10 | permissions:
11 | contents: write
12 | pull-requests: write
13 |
14 | name: release-please
15 |
16 | jobs:
17 | release-please:
18 | runs-on: ubuntu-latest
19 | outputs:
20 | release_created: ${{ steps.release.outputs.release_created }}
21 | upload_url: ${{ steps.release.outputs.upload_url }}
22 | tag_name: ${{ steps.release.outputs.tag_name }}
23 | steps:
24 | - uses: google-github-actions/release-please-action@v3
25 | id: release
26 | with:
27 | command: manifest
28 |
29 | upload-build:
30 | runs-on: ubuntu-latest
31 | needs: release-please
32 | if: ${{ needs.release-please.outputs.release_created }}
33 | env:
34 | upload_url: ${{ needs.release-please.outputs.upload_url }}
35 | tag_name: ${{ needs.release-please.outputs.tag_name }}
36 | steps:
37 | - uses: actions/checkout@v3
38 | - name: Use Node.js
39 | uses: actions/setup-node@v3
40 | with:
41 | node-version: "18.x" # You might need to adjust this value to your own version
42 | - name: Build
43 | id: build
44 | run: |
45 | npm install
46 | npm run build --if-present
47 | mkdir ${{ env.PLUGIN_NAME }}
48 | cp main.js manifest.json styles.css ${{ env.PLUGIN_NAME }}
49 | zip -r ${{ env.PLUGIN_NAME }}.zip ${{ env.PLUGIN_NAME }}
50 | - name: Upload zip file
51 | id: upload-zip
52 | uses: actions/upload-release-asset@v1
53 | env:
54 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
55 | with:
56 | upload_url: ${{ env.upload_url }}
57 | asset_path: ./${{ env.PLUGIN_NAME }}.zip
58 | asset_name: ${{ env.PLUGIN_NAME }}-${{ env.tag_name }}.zip
59 | asset_content_type: application/zip
60 | - name: Upload main.js
61 | id: upload-main
62 | uses: actions/upload-release-asset@v1
63 | env:
64 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
65 | with:
66 | upload_url: ${{ env.upload_url }}
67 | asset_path: ./main.js
68 | asset_name: main.js
69 | asset_content_type: text/javascript
70 | - name: Upload manifest.json
71 | id: upload-manifest
72 | uses: actions/upload-release-asset@v1
73 | env:
74 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
75 | with:
76 | upload_url: ${{ env.upload_url }}
77 | asset_path: ./manifest.json
78 | asset_name: manifest.json
79 | asset_content_type: application/json
80 | - name: Upload styles.css
81 | id: upload-css
82 | uses: actions/upload-release-asset@v1
83 | env:
84 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
85 | with:
86 | upload_url: ${{ env.upload_url }}
87 | asset_path: ./styles.css
88 | asset_name: styles.css
89 | asset_content_type: text/css
90 | notify:
91 | needs: upload-build
92 | uses: javalent/workflows/.github/workflows/notify.yml@main
93 | secrets: inherit
94 | with:
95 | name: Settings Search
96 | repo: settings-search
97 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Intellij
2 | *.iml
3 | .idea
4 |
5 | # npm
6 | node_modules
7 |
8 | # build
9 | main.js
10 | *.js.map
11 | dist
12 | styles.css
13 |
14 | # obsidian
15 | data.json
16 | .DS_Store
17 | *dev.js
18 | main.js.LICENSE.txt
19 |
20 | .env
--------------------------------------------------------------------------------
/.release-please-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | ".": "1.3.10"
3 | }
4 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4 |
5 | ## [1.3.10](https://github.com/javalent/settings-search/compare/1.3.9...1.3.10) (2023-10-05)
6 |
7 |
8 | ### Bug Fixes
9 |
10 | * fix error that occurs when plugins are loaded after this one (close [#43](https://github.com/javalent/settings-search/issues/43), close [#44](https://github.com/javalent/settings-search/issues/44)) ([2581772](https://github.com/javalent/settings-search/commit/2581772ad366e6659bd225aafc09e9d9f43c0c51))
11 |
12 | ## [1.3.9](https://github.com/javalent/settings-search/compare/1.3.8...1.3.9) (2023-10-03)
13 |
14 |
15 | ### Bug Fixes
16 |
17 | * Keep track of viewed plugin setting tabs ([65eb91c](https://github.com/javalent/settings-search/commit/65eb91cb5a734fd137b8e22ed46690300c48f77d))
18 |
19 | ## [1.3.8](https://github.com/javalent/settings-search/compare/1.3.7...1.3.8) (2023-05-30)
20 |
21 |
22 | ### Bug Fixes
23 |
24 | * **Readme:** Update Readme to 1.0 ([ecefe4d](https://github.com/javalent/settings-search/commit/ecefe4d1103a597e4949f9a8c3b73575c7fd6012)), closes [#35](https://github.com/javalent/settings-search/issues/35)
25 |
26 | ### [1.3.7](https://github.com/valentine195/obsidian-settings-search/compare/1.3.6...1.3.7) (2023-03-20)
27 |
28 |
29 | ### Bug Fixes
30 |
31 | * adds Ctrl+P / Ctrl+N to navigate results (close [#32](https://github.com/valentine195/obsidian-settings-search/issues/32)) ([44a4c99](https://github.com/valentine195/obsidian-settings-search/commit/44a4c995fb9a4ba4e24872f346a484afe678e0d3))
32 |
33 | ### [1.3.5](https://github.com/valentine195/obsidian-settings-search/compare/1.3.4...1.3.5) (2023-01-20)
34 |
35 |
36 | ### Bug Fixes
37 |
38 | * adds setting search loaded event ([ae4c2fb](https://github.com/valentine195/obsidian-settings-search/commit/ae4c2fb5a1572feb76fbfe540653f15e659e7157))
39 |
40 | ### [1.3.3](https://github.com/valentine195/obsidian-settings-search/compare/1.3.2...1.3.3) (2023-01-20)
41 |
42 |
43 | ### Bug Fixes
44 |
45 | * expose removeTabResources ([8eb6990](https://github.com/valentine195/obsidian-settings-search/commit/8eb6990c92b6e08cca95e1f9f76d30848ffb9754))
46 |
47 | ### [1.3.2](https://github.com/valentine195/obsidian-settings-search/compare/1.3.1...1.3.2) (2023-01-20)
48 |
49 |
50 | ### Bug Fixes
51 |
52 | * addResource -> addResources, removeResource -> removeResources, addResources now returns a deregister function ([1afdb45](https://github.com/valentine195/obsidian-settings-search/commit/1afdb45a8d5f4edba2c7e096d2de756bf7f29169))
53 |
54 | ### [1.3.1](https://github.com/valentine195/obsidian-settings-search/compare/1.3.0...1.3.1) (2023-01-20)
55 |
56 |
57 | ### Bug Fixes
58 |
59 | * adds a way to remove external resources ([fe72419](https://github.com/valentine195/obsidian-settings-search/commit/fe724191a3b57d1d3558f5a024c0892aa3c2bd22))
60 |
61 | ## [1.3.0](https://github.com/valentine195/obsidian-settings-search/compare/1.2.0...1.3.0) (2023-01-19)
62 |
63 |
64 | ### Features
65 |
66 | * adds ability for other plugins to add settings ([f35ee83](https://github.com/valentine195/obsidian-settings-search/commit/f35ee83d98ccb1f504d08ea7674895b7414a6403))
67 |
68 |
69 | ### Bug Fixes
70 |
71 | * fixes jump to setting ([27e2b41](https://github.com/valentine195/obsidian-settings-search/commit/27e2b4150df338087d43fc67a6b14c3f010a6a9b))
72 |
73 | ## [1.2.0](https://github.com/valentine195/obsidian-settings-search/compare/1.1.0...1.2.0) (2022-03-23)
74 |
75 |
76 | ### Features
77 |
78 | * Settings Search is now on mobile! closes [#9](https://github.com/valentine195/obsidian-settings-search/issues/9) ([8838d41](https://github.com/valentine195/obsidian-settings-search/commit/8838d41f41428d89cdead96bd68a692834aa446c))
79 |
80 | ## [1.1.0](https://github.com/valentine195/obsidian-settings-search/compare/1.0.7...1.1.0) (2022-03-17)
81 |
82 |
83 | ### Features
84 |
85 | * Added keyboard navigation to search results (close [#3](https://github.com/valentine195/obsidian-settings-search/issues/3)) ([f178598](https://github.com/valentine195/obsidian-settings-search/commit/f17859842d8d79237e9c64a6a1df818fb31dfa94))
86 |
87 | ### [1.0.7](https://github.com/valentine195/obsidian-settings-search/compare/1.0.6...1.0.7) (2022-03-15)
88 |
89 |
90 | ### Bug Fixes
91 |
92 | * Wait for SettingTab display call before grabbing settings (close [#13](https://github.com/valentine195/obsidian-settings-search/issues/13)) ([b1adc7f](https://github.com/valentine195/obsidian-settings-search/commit/b1adc7f35a36bfe6424e88665e418bce66b1ff2c))
93 |
94 | ### [1.0.6](https://github.com/valentine195/obsidian-settings-search/compare/1.0.5...1.0.6) (2022-03-10)
95 |
96 |
97 | ### Bug Fixes
98 |
99 | * Adds check for nested details elements (close [#11](https://github.com/valentine195/obsidian-settings-search/issues/11)) ([0d07279](https://github.com/valentine195/obsidian-settings-search/commit/0d072796f9613357a8462a98431db89d2b7e4f29))
100 | * Fixes padding issue on default theme (close [#12](https://github.com/valentine195/obsidian-settings-search/issues/12)) ([13a3e98](https://github.com/valentine195/obsidian-settings-search/commit/13a3e985519de3cc01ee4b61f55e3e0b53d03a03))
101 |
102 | ### [1.0.5](https://github.com/valentine195/obsidian-settings-search/compare/1.0.4...1.0.5) (2022-03-03)
103 |
104 |
105 | ### Bug Fixes
106 |
107 | * Hotkeys are now always at the bottom of results (close [#10](https://github.com/valentine195/obsidian-settings-search/issues/10)) ([625d64e](https://github.com/valentine195/obsidian-settings-search/commit/625d64e4af28c559a017c1d07075d1f0e3c3fefd))
108 |
109 | ### [1.0.4](https://github.com/valentine195/obsidian-settings-search/compare/1.0.3...1.0.4) (2022-02-23)
110 |
111 |
112 | ### Bug Fixes
113 |
114 | * Fixed settings search placeholder ([a71e4bb](https://github.com/valentine195/obsidian-settings-search/commit/a71e4bb99fdf1aa40ddfa17c0e64d79bcd5cc2b5))
115 |
116 | ### [1.0.3](https://github.com/valentine195/obsidian-settings-search/compare/1.0.2...1.0.3) (2022-02-23)
117 |
118 |
119 | ### Bug Fixes
120 |
121 | * Loading plugin no longer closes the active settings tab ([317c1e2](https://github.com/valentine195/obsidian-settings-search/commit/317c1e2ad104f36d044c1c9ecc8e0182a0c16c96))
122 | * Settings indexing is no longer blocks the main process ([ee7b7b9](https://github.com/valentine195/obsidian-settings-search/commit/ee7b7b947cf3e00c987e122141e5ab19155a830b))
123 | * Unloading the plugin no longer re-loads the active settings tab ([5579a8d](https://github.com/valentine195/obsidian-settings-search/commit/5579a8d5d55a85465431509e9ce33da3040707fc))
124 |
125 | ### [1.0.2](https://github.com/valentine195/obsidian-settings-search/compare/1.0.1...1.0.2) (2022-02-17)
126 |
127 |
128 | ### Bug Fixes
129 |
130 | * call hide() on settings tabs ([78ea4c9](https://github.com/valentine195/obsidian-settings-search/commit/78ea4c9fc9de8bd1307607ec9ae9c27cf3429c8f))
131 | * calls tab.hide after building settings tab resources to properly unload ([7de69f6](https://github.com/valentine195/obsidian-settings-search/commit/7de69f65d5e5fe040c090199f45af32a7fcd6010))
132 |
133 | ### [1.0.1](https://github.com/valentine195/obsidian-settings-search/compare/1.0.0...1.0.1) (2022-02-15)
134 |
135 |
136 | ### Bug Fixes
137 |
138 | * fixed issue where settings with no descriptions would break fuzzy search (close [#6](https://github.com/valentine195/obsidian-settings-search/issues/6)) ([fecf025](https://github.com/valentine195/obsidian-settings-search/commit/fecf02500f90437487aed33133c1cf4ae1ad3b24))
139 |
140 | ## [1.0.0](https://github.com/valentine195/obsidian-settings-search/compare/0.0.2...1.0.0) (2022-02-14)
141 |
142 |
143 | ### Features
144 |
145 | * can now open plugin settings directly from search results (close [#4](https://github.com/valentine195/obsidian-settings-search/issues/4)) ([a0d6b6f](https://github.com/valentine195/obsidian-settings-search/commit/a0d6b6f591b0126243287cb09fc407ee5398b2b9))
146 |
147 |
148 | ### Bug Fixes
149 |
150 | * patches setting open to focus search field on open (close [#2](https://github.com/valentine195/obsidian-settings-search/issues/2)) ([1b6561e](https://github.com/valentine195/obsidian-settings-search/commit/1b6561e690061df0eaf535ee55f336fd369a2378))
151 | * settings descriptions are now recreated as-is (closes [#5](https://github.com/valentine195/obsidian-settings-search/issues/5)) ([2b3678c](https://github.com/valentine195/obsidian-settings-search/commit/2b3678c8730fd0d7fd5fe8dbffa413e2e58b0f1d))
152 |
153 | ### 0.0.2 (2022-02-10)
154 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 valentine195
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 | > 🥇 Our documentation has moved ***[here](https://plugins.javalent.com/settings-search)***.
2 | >
3 | >
4 | >
5 | > ---
6 | >
7 | > **Development Status**: Maintenance Mode
8 | >
9 | > Due to a glut of high priority Javalent plugin projects, this plugin is now entering maintenance mode for the time being. This is **not** a permanent status.
10 | > - PR's will be reviewed.
11 | > - *Yay* bugs will be reviewed and worked if able.
12 | > - Feature Requests **will not** be worked.
13 |
14 | The Settings Search plugin adds global search functionality to the Obsidian settings, allowing users to quickly search for a particular setting. Keyboard navigation support is provided for ease of use.
15 | Additionally, plugin authors can add their settings dynamically using the API provided by the plugin.
16 | The plugin API includes three functions for this purpose: addResources(), removeResources(), and removeTabResources().
17 |
18 | ## Features
19 | - Global search functionality added to the Obsidian settings.
20 | - Keyboard navigation support using the up and down arrow keys to move through search results and enter to go to the setting.
21 | - Dynamic settings rendering support using the API for plugin authors to add their settings dynamically.
22 |
23 | ### Quickstart
24 |
25 | 1. Install the plugin from the Obsidian community plugins browser.
26 | 2. Go to Obsidian Settings
27 | 3. Search! That's it. That's the plugin.
28 |
29 | Check out the **[plugin documentation](https://plugins.javalent.com/settings-search)** for more detailed instructions and examples.
30 |
31 | ## Support
32 |
33 | If you encounter any issues, want to give back and help out, or have suggestions for new features, file an issue on the **[GitHub repository](https://github.com/javalent/settings-search)**.
34 |
35 | ### Complementary plugins
36 | If you're using Obsidian to better organize your notes, you may find some of my other plugins useful:
37 |
38 | - [Admonitions](https://github.com/javalent/admonitions) Create and customize code-block callouts.
39 | - [Prominent Bookmarked Files](https://github.com/javalent/prominent-files) Make your Bookmarked files stand out better on the file pane.
40 | - [Markdown Attributes](https://github.com/javalent/markdown-attributes) Add classes and IDs to Markdown elements.
41 |
--------------------------------------------------------------------------------
/assets/ui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javalent/settings-search/8f9090ef354b9e927b6fe0ff6b7147618b531702/assets/ui.png
--------------------------------------------------------------------------------
/esbuild.config.mjs:
--------------------------------------------------------------------------------
1 | import esbuild from "esbuild";
2 | import process from "process";
3 | import builtins from "builtin-modules";
4 | import { config } from "dotenv";
5 |
6 | config();
7 |
8 | const banner = `/*
9 | THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
10 | if you want to view the source, please visit the github repository of this plugin
11 | */
12 | `;
13 |
14 | const prod = process.argv[2] === "production";
15 |
16 | const dir = prod ? "./" : process.env.OUTDIR;
17 |
18 | esbuild
19 | .build({
20 | banner: {
21 | js: banner
22 | },
23 | entryPoints: ["src/main.ts", "src/styles.css"],
24 | bundle: true,
25 | external: ["obsidian", "electron", ...builtins],
26 | format: "cjs",
27 | watch: !prod,
28 | target: "es2020",
29 | logLevel: "info",
30 | sourcemap: prod ? false : "inline",
31 | treeShaking: true,
32 | outdir: dir
33 | })
34 | .catch(() => process.exit(1));
35 |
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "settings-search",
3 | "name": "Settings Search",
4 | "version": "1.3.10",
5 | "minAppVersion": "0.12.17",
6 | "author": "Jeremy Valentine",
7 | "description": "Globally search settings in Obsidian.md",
8 | "authorUrl": "https://github.com/valentine195",
9 | "isDesktopOnly": false
10 | }
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "settings-search",
3 | "version": "1.3.10",
4 | "description": "Globally search settings in Obsidian.md",
5 | "main": "main.js",
6 | "scripts": {
7 | "dev": "node esbuild.config.mjs",
8 | "build": "node esbuild.config.mjs production",
9 | "web:dev": "set NODE_OPTIONS=--openssl-legacy-provider && webpack --config webpack.dev.js -w",
10 | "web:build": "webpack",
11 | "rollup:dev": "rollup --config rollup.config-dev.js -w",
12 | "rollup:build": "rollup --config rollup.config.js",
13 | "test": "jest",
14 | "release": "standard-version",
15 | "push": "git push --follow-tags origin"
16 | },
17 | "standard-version": {
18 | "t": "",
19 | "releaseCommitMessageFormat": "Settings Search Release: v{{currentTag}}"
20 | },
21 | "keywords": [],
22 | "author": "",
23 | "license": "MIT",
24 | "devDependencies": {
25 | "@babel/core": "^7.15.8",
26 | "@babel/preset-env": "^7.15.8",
27 | "@types/node": "^14.17.21",
28 | "builtin-modules": "^3.2.0",
29 | "dotenv": "^10.0.0",
30 | "esbuild": "^0.13.15",
31 | "monkey-around": "^2.3.0",
32 | "obsidian": "latest",
33 | "standard-version": "^9.3.2",
34 | "tslib": "^2.3.0",
35 | "typescript": "^4.2.4"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/publish/gif/settings-search-enter.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javalent/settings-search/8f9090ef354b9e927b6fe0ff6b7147618b531702/publish/gif/settings-search-enter.gif
--------------------------------------------------------------------------------
/publish/gif/settings-search-keyboard.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javalent/settings-search/8f9090ef354b9e927b6fe0ff6b7147618b531702/publish/gif/settings-search-keyboard.gif
--------------------------------------------------------------------------------
/publish/gif/settings-search-settings-search-highlight.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javalent/settings-search/8f9090ef354b9e927b6fe0ff6b7147618b531702/publish/gif/settings-search-settings-search-highlight.gif
--------------------------------------------------------------------------------
/publish/gif/settings-search-up-down.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javalent/settings-search/8f9090ef354b9e927b6fe0ff6b7147618b531702/publish/gif/settings-search-up-down.gif
--------------------------------------------------------------------------------
/release-please-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "packages": {
3 | ".": {
4 | "changelog-path": "CHANGELOG.md",
5 | "release-type": "node"
6 | }
7 | },
8 | "include-component-in-tag": false,
9 | "include-v-in-tag": false,
10 | "extra-files": [
11 | {
12 | "type": "json",
13 | "path": "manifest.json",
14 | "jsonpath": "$.version"
15 | }
16 | ],
17 | "last-release-sha": "ab8efeaa667ab0b88d2af0ca37c686b526ea1ee0",
18 |
19 | "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json"
20 | }
21 |
--------------------------------------------------------------------------------
/src/assets/main.css:
--------------------------------------------------------------------------------
1 | .settings-search-container.vertical-tab-header-group {
2 | padding-bottom: 0;
3 | }
4 | .settings-search-input {
5 | padding-left: 6px;
6 | }
7 | .vertical-tab-nav-item.settings-search-input {
8 | background-color: inherit !important;
9 | }
10 | .settings-search-input .setting-item-control {
11 | display: block;
12 | }
13 | .settings-search-input .search-input-container {
14 | margin: 0;
15 | }
16 |
17 | .settings-search-results .setting-item.active {
18 | background-color: var(--background-secondary);
19 | }
20 |
21 | .settings-search-results .set-externally .setting-item-name {
22 | display: flex;
23 | gap: 0.5rem;
24 | }
25 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Plugin,
3 | SearchComponent,
4 | Setting,
5 | SearchResult,
6 | Notice,
7 | prepareSimpleSearch,
8 | SettingTab,
9 | Scope,
10 | Platform,
11 | setIcon
12 | } from "obsidian";
13 |
14 | import { around } from "monkey-around";
15 |
16 | declare module "obsidian" {
17 | interface App {
18 | internalPlugins: {
19 | plugins: Record<
20 | string,
21 | { _loaded: boolean; instance: { name: string; id: string } }
22 | >;
23 | };
24 | plugins: {
25 | manifests: Record;
26 | plugins: Record;
27 | getPlugin(id: string): Plugin;
28 | };
29 | setting: {
30 | onOpen(): void;
31 | onClose(): void;
32 |
33 | openTabById(id: string): void;
34 | openTab(tab: SettingTab): void;
35 |
36 | isPluginSettingTab(tab: SettingTab): boolean;
37 | addSettingTab(tab: SettingTab): void;
38 | removeSettingTab(tab: SettingTab): void;
39 |
40 | activeTab: SettingTab;
41 | lastTabId: string;
42 |
43 | pluginTabs: PluginSettingTab[];
44 | settingTabs: SettingTab[];
45 |
46 | tabContentContainer: HTMLDivElement;
47 | tabHeadersEl: HTMLDivElement;
48 | };
49 | }
50 | interface Plugin {
51 | _loaded: boolean;
52 | }
53 | interface PluginSettingTab {
54 | name: string;
55 | }
56 | interface SettingTab {
57 | id: string;
58 | name: string;
59 | navEl: HTMLElement;
60 | }
61 | interface Workspace {
62 | on(
63 | name: "settings-search-loaded",
64 | callback: (...args: any[]) => any
65 | ): EventRef;
66 | }
67 | }
68 | declare global {
69 | interface Window {
70 | SettingsSearch?: {
71 | addResources: SettingsSearch["addResources"];
72 | removeResources: SettingsSearch["removeResources"];
73 | removeTabResources: SettingsSearch["removeTabResources"];
74 | };
75 | }
76 | }
77 |
78 | interface Resource {
79 | tab: string;
80 | //tab name
81 | name: string;
82 | text: string;
83 | desc: string;
84 | external?: boolean;
85 | }
86 | export default class SettingsSearch extends Plugin {
87 | settingsSearchEl: HTMLDivElement = createDiv(
88 | "settings-search-container vertical-tab-header-group"
89 | );
90 | settingsResultsContainerEl = createDiv(
91 | "settings-search-results-container vertical-tab-content"
92 | );
93 | settingsNavItemContainer = this.settingsSearchEl
94 | .createDiv("vertical-tab-header-group-items")
95 | .createDiv("vertical-tab-nav-item settings-search-input");
96 | settingsResultsEl: HTMLDivElement;
97 | search: SearchComponent;
98 | locale: string;
99 |
100 | resources: Resource[] = [];
101 | results: Resource[] = [];
102 |
103 | async onload() {
104 | (window["SettingsSearch"] = {
105 | addResources: this.addResources.bind(this),
106 | removeResources: this.removeResources.bind(this),
107 | removeTabResources: this.removeTabResources.bind(this)
108 | }) && this.register(() => delete window["SettingsSearch"]);
109 |
110 | this.app.workspace.onLayoutReady(async () => {
111 | this.settingsResultsContainerEl.createEl("h3", {
112 | text: "Settings Search Results"
113 | });
114 | this.settingsResultsEl = this.settingsResultsContainerEl.createDiv(
115 | "settings-search-results"
116 | );
117 |
118 | this.buildScope();
119 | this.buildSearch();
120 | this.buildResources();
121 | this.buildPluginResources();
122 | this.patchSettings();
123 | this.loaded = true;
124 | this.app.workspace.trigger("settings-search-loaded");
125 | });
126 | }
127 | loaded = false;
128 | tabIndex = 0;
129 | pluginTabIndex = 0;
130 | seen: string[] = [];
131 | buildResources() {
132 | const tab = this.app.setting.settingTabs[this.tabIndex];
133 | if (tab && tab.id !== undefined && (!this.seen.includes(tab.id))) {
134 | this.getTabResources(tab);
135 | this.tabIndex++;
136 | setTimeout(() => this.buildResources());
137 | }
138 | }
139 | buildPluginResources() {
140 | const tab = this.app.setting.pluginTabs[this.pluginTabIndex];
141 | if (tab) {
142 | this.getTabResources(tab);
143 | this.pluginTabIndex++;
144 | setTimeout(() => this.buildPluginResources());
145 | }
146 | }
147 | get manifests() {
148 | return Object.values(this.app.plugins.manifests);
149 | }
150 | settingCache: Map = new Map();
151 | public addResourceToCache(resource: Resource) {
152 | if (!resource || !resource.text || !resource.name || !resource.tab) {
153 | return new Error("A valid resource must be provided.");
154 | }
155 |
156 | let name: DocumentFragment | string;
157 | if (resource.external) {
158 | name = createFragment((el) => {
159 | setIcon(
160 | el.createSpan({
161 | attr: {
162 | "aria-label":
163 | "This setting was added by another plugin."
164 | }
165 | }),
166 | "info"
167 | );
168 | el.createSpan({ text: resource.text });
169 | });
170 | } else {
171 | name = resource.text;
172 | }
173 | const setting = new Setting(createDiv())
174 | .setName(name)
175 | .setDesc(
176 | createFragment(
177 | (e) => (e.createDiv().innerHTML = resource.desc ?? "")
178 | )
179 | );
180 | if (resource.external) {
181 | setting.settingEl.addClass("set-externally");
182 | }
183 | if (resource.tab == "community-plugins") {
184 | let plugin = this.manifests.find((p) => p.name == resource.text);
185 | if (
186 | plugin &&
187 | this.app.plugins.getPlugin(plugin.id)?._loaded &&
188 | this.app.setting.pluginTabs.find((t) => t.id == plugin.id)
189 | ) {
190 | setting.addExtraButton((b) => {
191 | b.setTooltip(`Open ${resource.text} Settings`).onClick(
192 | () => {
193 | this.app.setting.openTabById(plugin.id);
194 | }
195 | );
196 | });
197 | }
198 | }
199 | if (resource.tab == "plugins") {
200 | const plugins = Object.values(this.app.internalPlugins.plugins);
201 | const plugin = plugins.find(
202 | (p) => p._loaded && p.instance.name == resource.text
203 | );
204 |
205 | if (
206 | plugin &&
207 | this.app.setting.pluginTabs.find(
208 | (t) => t.id == plugin.instance.id
209 | )
210 | ) {
211 | setting.addExtraButton((b) => {
212 | b.setTooltip(`Open ${resource.text} Settings`).onClick(
213 | () => {
214 | this.app.setting.openTabById(plugin.instance.id);
215 | }
216 | );
217 | });
218 | }
219 | }
220 | setting.addExtraButton((b) => {
221 | b.setIcon("forward-arrow").onClick(() => {
222 | this.showResult(resource);
223 | });
224 | });
225 | this.settingCache.set(resource, setting);
226 | }
227 | getResourceFromCache(resource: Resource) {
228 | if (!this.settingCache.has(resource)) {
229 | this.addResourceToCache(resource);
230 | }
231 | return this.settingCache.get(resource);
232 | }
233 | removeResourcesFromCache(resources: Resource[]) {
234 | for (const resource of resources) {
235 | this.settingCache.delete(resource);
236 | }
237 | }
238 | addResources(...resources: Resource[]) {
239 | for (const resource of resources) {
240 | resource.external = true;
241 | if (this.resources.find((k) => this.equivalent(resource, k)))
242 | continue;
243 | this.resources.push(resource);
244 | this.addResourceToCache(resource);
245 | }
246 | return () => this.removeResources(...resources);
247 | }
248 | equivalent(resource1: Resource, resource2: Resource) {
249 | return (
250 | resource1.name == resource2.name &&
251 | resource1.tab == resource2.tab &&
252 | resource1.text == resource2.text &&
253 | resource1.desc == resource2.desc &&
254 | resource1.external == resource2.external
255 | );
256 | }
257 | removeResources(...resources: Resource[]) {
258 | const removing = [];
259 | const keys = [...this.settingCache.keys()];
260 | for (const resource of resources) {
261 | if (
262 | !resource ||
263 | !resource.text ||
264 | !resource.name ||
265 | !resource.tab
266 | ) {
267 | continue;
268 | }
269 | resource.external = true;
270 | this.resources = this.resources.filter(
271 | (r) => !this.equivalent(resource, r)
272 | );
273 | removing.push(
274 | ...keys.filter(
275 | (k) => k == resource || this.equivalent(resource, k)
276 | )
277 | );
278 | }
279 | this.removeResourcesFromCache(removing);
280 | }
281 | removeTabResources(tab: string) {
282 | const removing = this.resources.filter((t) => t.tab == tab);
283 | this.resources = this.resources.filter((t) => t.tab != tab);
284 | this.removeResourcesFromCache(removing);
285 | }
286 | async getTabResources(tab: SettingTab) {
287 | await tab.display();
288 | const settings = tab.containerEl.querySelectorAll(
289 | ".setting-item:not(.setting-item-header)"
290 | );
291 | for (const el of Array.from(settings)) {
292 | const text =
293 | el.querySelector(
294 | ".setting-item-name"
295 | )?.textContent;
296 | if (!text) continue;
297 |
298 | const desc =
299 | el.querySelector(".setting-item-description")
300 | ?.innerHTML ?? "";
301 |
302 | const resource = {
303 | tab: tab.id,
304 | name: tab.name,
305 | text,
306 | desc
307 | };
308 | this.resources.push(resource);
309 | this.addResourceToCache(resource);
310 | }
311 | if (this.app.setting.activeTab?.id == tab.id) return;
312 | this.seen.push(tab.id);
313 | tab.containerEl.detach();
314 | tab.hide();
315 | }
316 | patchSettings() {
317 | const self = this;
318 |
319 | this.register(
320 | around(this.app.setting, {
321 | onOpen: function (next) {
322 | return function () {
323 | next.apply(this);
324 | if (!Platform.isMobile) self.search.inputEl.focus();
325 | return next;
326 | };
327 | }
328 | })
329 | );
330 |
331 | //Patch addSettingTab to capture changes to plugin settings.
332 | this.register(
333 | around(this.app.setting, {
334 | addSettingTab: function (next) {
335 | return function (tab: SettingTab) {
336 | if (tab && tab.id !== undefined && (!self.seen.includes(tab.id))) {
337 | self.getTabResources(tab);
338 | }
339 | return next.call(this, tab);
340 | };
341 | }
342 | })
343 | );
344 |
345 | //Patch removeSettingTab to capture changes to plugin settings.
346 | this.register(
347 | around(this.app.setting, {
348 | removeSettingTab: function (next) {
349 | return function (tab: SettingTab) {
350 | if (this.isPluginSettingTab(tab)) {
351 | self.removeTabResources(tab.id);
352 | }
353 | return next.call(this, tab);
354 | };
355 | }
356 | })
357 | );
358 |
359 | this.register(
360 | around(this.app.setting, {
361 | openTab: function (next) {
362 | return function (tab: SettingTab) {
363 | self.searchAppended = false;
364 | self.app.keymap.popScope(self.scope);
365 | return next.call(this, tab);
366 | };
367 | },
368 | openTabById: function (next) {
369 | return function (tab: string) {
370 | self.searchAppended = false;
371 | self.app.keymap.popScope(self.scope);
372 | return next.call(this, tab);
373 | };
374 | },
375 | onClose: function (next) {
376 | return function () {
377 | if (Platform.isMobile) {
378 | self.detach();
379 | }
380 | return next.call(this);
381 | };
382 | }
383 | })
384 | );
385 | }
386 |
387 | buildSearch() {
388 | const tempSetting = new Setting(createDiv()).addSearch((s) => {
389 | this.search = s;
390 | });
391 |
392 | this.settingsNavItemContainer.append(tempSetting.controlEl);
393 |
394 | tempSetting.settingEl.detach();
395 |
396 | this.search.onChange((v) => {
397 | this.onChange(v);
398 | });
399 | this.search.setPlaceholder("Search settings...");
400 | this.app.setting.tabHeadersEl.prepend(this.settingsSearchEl);
401 | }
402 |
403 | searchAppended = false;
404 | activeIndex = -1;
405 | activeSetting: Setting;
406 | scope = new Scope(this.app.scope);
407 | buildScope() {
408 | this.scope.register(["Ctrl"], "N", () => {
409 | if (this.activeSetting) {
410 | this.activeSetting.settingEl.removeClass("active");
411 | }
412 | this.activeIndex =
413 | (((this.activeIndex + 1) % this.results.length) +
414 | this.results.length) %
415 | this.results.length;
416 |
417 | this.centerActiveSetting();
418 | });
419 | this.scope.register([], "ArrowDown", () => {
420 | if (this.activeSetting) {
421 | this.activeSetting.settingEl.removeClass("active");
422 | }
423 | this.activeIndex =
424 | (((this.activeIndex + 1) % this.results.length) +
425 | this.results.length) %
426 | this.results.length;
427 |
428 | this.centerActiveSetting();
429 | });
430 | this.scope.register(["Ctrl"], "P", () => {
431 | if (this.activeSetting) {
432 | this.activeSetting.settingEl.removeClass("active");
433 | }
434 | this.activeIndex =
435 | (((this.activeIndex - 1) % this.results.length) +
436 | this.results.length) %
437 | this.results.length;
438 |
439 | this.centerActiveSetting();
440 | });
441 | this.scope.register([], "ArrowUp", () => {
442 | if (this.activeSetting) {
443 | this.activeSetting.settingEl.removeClass("active");
444 | }
445 | this.activeIndex =
446 | (((this.activeIndex - 1) % this.results.length) +
447 | this.results.length) %
448 | this.results.length;
449 |
450 | this.centerActiveSetting();
451 | });
452 | this.scope.register([], "Enter", () => {
453 | if (this.activeSetting) {
454 | this.showResult(this.results[this.activeIndex]);
455 | }
456 | });
457 | }
458 |
459 | centerActiveSetting() {
460 | const result = this.results[this.activeIndex];
461 | this.activeSetting = this.getResourceFromCache(result);
462 | this.activeSetting.settingEl.addClass("active");
463 |
464 | this.activeSetting.settingEl.scrollIntoView({
465 | behavior: "auto",
466 | block: "nearest"
467 | });
468 | }
469 | mobileContainers: HTMLElement[] = [];
470 | detachFromMobile() {
471 | if (Platform.isMobile) {
472 | this.settingsResultsContainerEl.detach();
473 | for (const header of this.mobileContainers) {
474 | this.app.setting.tabHeadersEl.append(header);
475 | }
476 | this.search.setValue("");
477 | }
478 | }
479 | detachFromDesktop() {
480 | if (Platform.isDesktop) {
481 | this.app.setting.openTabById(this.app.setting.lastTabId);
482 | }
483 | }
484 | detach() {
485 | this.detachFromDesktop();
486 | this.detachFromMobile();
487 | this.searchAppended = false;
488 | }
489 | onChange(v: string) {
490 | if (!v) {
491 | this.detach();
492 | this.app.keymap.popScope(this.scope);
493 | return;
494 | }
495 | if (!this.searchAppended) {
496 | this.activeIndex = -1;
497 | this.app.keymap.popScope(this.scope);
498 | this.app.keymap.pushScope(this.scope);
499 | if (this.activeSetting) {
500 | this.activeSetting.settingEl.removeClass("active");
501 | this.activeSetting = null;
502 | }
503 |
504 | if (!Platform.isMobile) {
505 | this.app.setting.activeTab.navEl.removeClass("is-active");
506 | this.app.setting.tabContentContainer.empty();
507 | this.app.setting.tabContentContainer.append(
508 | this.settingsResultsContainerEl
509 | );
510 | } else {
511 | const headers =
512 | this.app.setting.tabHeadersEl.querySelectorAll(
513 | ".vertical-tab-header-group:not(.settings-search-container)"
514 | );
515 | for (const header of Array.from(headers)) {
516 | this.mobileContainers.push(header);
517 | header.detach();
518 | }
519 | this.app.setting.tabHeadersEl.append(
520 | this.settingsResultsContainerEl
521 | );
522 | }
523 | this.searchAppended = true;
524 | }
525 | this.appendResults(this.performFuzzySearch(v));
526 | }
527 | getMatchText(text: string, result: SearchResult) {
528 | const matchElements: Record = {};
529 | return createFragment((content) => {
530 | for (let i = 0; i < text.length; i++) {
531 | let match = result.matches.find((m) => m[0] === i);
532 | if (match) {
533 | const index = result.matches.indexOf(match);
534 | if (!matchElements[index]) {
535 | matchElements[index] = createSpan(
536 | "suggestion-highlight"
537 | );
538 | }
539 | let element = matchElements[index];
540 | content.appendChild(element);
541 | element.appendText(text.substring(match[0], match[1]));
542 |
543 | i += match[1] - match[0] - 1;
544 | continue;
545 | }
546 |
547 | content.appendText(text[i]);
548 | }
549 | });
550 | }
551 | appendResults(results: Resource[]) {
552 | this.settingsResultsEl.empty();
553 | if (results.length) {
554 | const headers: Record = {};
555 | for (const resource of results) {
556 | if (!(resource.tab in headers)) {
557 | headers[resource.tab] = this.settingsResultsEl.createDiv();
558 |
559 | new Setting(headers[resource.tab])
560 | .setHeading()
561 | .setName(resource.name);
562 | }
563 |
564 | const setting = this.getResourceFromCache(resource);
565 |
566 | headers[resource.tab].append(setting.settingEl);
567 | }
568 | /* if ("hotkeys" in headers) {
569 | this.settingsResultsEl.appendChild(headers["hotkeys"]);
570 | } */
571 | } else {
572 | this.settingsResultsEl.setText("No results found :(");
573 | }
574 | }
575 |
576 | showResult(result: Resource) {
577 | this.search.setValue("");
578 | const tab =
579 | this.app.setting.settingTabs.find((t) => t.id == result.tab) ??
580 | this.app.setting.pluginTabs.find((t) => t.id == result.tab);
581 | if (!tab) {
582 | new Notice("There was an issue opening the setting tab.");
583 | return;
584 | }
585 |
586 | this.app.setting.openTabById(tab.id);
587 | this.app.keymap.popScope(this.scope);
588 | this.detach();
589 |
590 | try {
591 | const names =
592 | tab.containerEl.querySelectorAll(".setting-item-name");
593 | const el = Array.from(names).find(
594 | (n) => n.textContent == result.text
595 | );
596 | if (!el) return;
597 |
598 | const setting = el.closest(".setting-item");
599 | if (!setting) return;
600 |
601 | if (tab.id == "obsidian-style-settings") {
602 | let collapsed = setting.closest(".style-settings-container");
603 | let previous = collapsed?.previousElementSibling;
604 |
605 | while (
606 | previous != null &&
607 | previous.hasClass("is-collapsed") &&
608 | previous.hasClass("style-settings-heading")
609 | ) {
610 | previous.removeClass("is-collapsed");
611 | collapsed = collapsed.parentElement?.closest(
612 | ".style-settings-container"
613 | );
614 | previous = collapsed?.previousElementSibling;
615 | }
616 | }
617 |
618 | let details = setting.closest("details");
619 | while (details) {
620 | details.setAttr("open", "open");
621 | details = details.parentElement?.closest("details");
622 | }
623 |
624 | setting.scrollIntoView(true);
625 |
626 | setting.addClass("is-flashing");
627 | window.setTimeout(() => setting.removeClass("is-flashing"), 3000);
628 | } catch (e) {
629 | console.error(e);
630 | }
631 | }
632 |
633 | performFuzzySearch(input: string) {
634 | const results: Resource[] = [],
635 | hotkeys: Resource[] = [];
636 | for (const resource of this.resources) {
637 | let result =
638 | prepareSimpleSearch(input)(resource.text) ??
639 | prepareSimpleSearch(input)(resource.desc);
640 | if (result) {
641 | if (resource.tab == "hotkeys") {
642 | hotkeys.push(resource);
643 | } else {
644 | results.push(resource);
645 | }
646 | }
647 | }
648 | this.results = [...results, ...hotkeys];
649 | return this.results;
650 | }
651 |
652 | onunload() {
653 | this.settingsSearchEl.detach();
654 |
655 | this.settingsResultsEl.detach();
656 | this.detach();
657 | if (this.searchAppended && Platform.isDesktop)
658 | this.app.setting.openTabById(this.app.setting.lastTabId);
659 | }
660 | }
661 |
--------------------------------------------------------------------------------
/src/styles.css:
--------------------------------------------------------------------------------
1 | @import './assets/main.css';
2 |
--------------------------------------------------------------------------------
/svelte.config.js:
--------------------------------------------------------------------------------
1 | const sveltePreprocess = require("svelte-preprocess");
2 |
3 | module.exports = {
4 | preprocess: sveltePreprocess(),
5 | };
6 |
7 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "module": "ESNext",
5 | "target": "esnext",
6 | "allowSyntheticDefaultImports": true,
7 | "esModuleInterop": true,
8 | "allowJs": true,
9 | "noImplicitAny": true,
10 | "moduleResolution": "node",
11 | "importHelpers": true,
12 | "types": ["node"],
13 | "lib": ["dom", "esnext", "scripthost", "es2015"]
14 | },
15 | "include": ["src/**/*"]
16 | }
17 |
--------------------------------------------------------------------------------
/versions.json:
--------------------------------------------------------------------------------
1 | {
2 | "0.0.1": "0.12.17"
3 | }
--------------------------------------------------------------------------------