diff --git a/src/global.d.ts b/src/global.d.ts index 6029016..0c07604 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -26,6 +26,9 @@ declare global { openExternal: (url: string) => Promise choosePluginsDir: () => Promise getPluginsDir: () => Promise + getPlugins: () => Promise + setDisabledPlugins: (pluginName: string, isDisabled: boolean) => Promise + getDisabledPlugins: () => Promise getHotkeys: () => Promise<{ [key: string]: string }> setHotkey: (type: string, hotkey: string) => Promise showMainWindow: () => Promise @@ -33,6 +36,13 @@ declare global { reloadApp: () => Promise } + type PluginT = { + name: string + label: string + version: string + author: string + } + type ApplicationT = { command: string isImmediate: boolean @@ -62,6 +72,13 @@ declare global { icon: string label: (query: string) => JSX.Element name: string + bang?: BangT + } + + export type BangT = { + bang: string + name: string + url: string } type ResultT = { diff --git a/src/main/handlers.ts b/src/main/handlers.ts index 29d3d7b..3a21900 100644 --- a/src/main/handlers.ts +++ b/src/main/handlers.ts @@ -48,9 +48,20 @@ const DEPS = { */ export const getCommands = async () => { const currentPluginsDir = await getPluginsDir() - const plugins = fs.readdirSync(currentPluginsDir).filter((plugin) => plugin !== '.git') + const disabledPlugins = await getDisabledPlugins() + const plugins = fs + .readdirSync(currentPluginsDir) + .filter((plugin) => plugin !== '.git') + .filter((plugin) => { + const pluginPath = path.join(currentPluginsDir, plugin) + return fs.statSync(pluginPath).isDirectory() + }) return plugins.flatMap((plugin) => { + if (disabledPlugins.includes(plugin)) { + return [] + } + const manifestPath = path.join(currentPluginsDir, plugin, 'manifest.yml') const manifest = yaml.load(fs.readFileSync(manifestPath, 'utf8')) as ManifestT @@ -289,6 +300,35 @@ export const getPluginsDir = (): Promise => { }) } +export const getPlugins = async (): Promise => { + const pluginsDir = await getPluginsDir() + const plugins = fs + .readdirSync(pluginsDir) + .filter((plugin) => plugin !== '.git') + .filter((plugin) => { + const pluginPath = path.join(pluginsDir, plugin) + return fs.statSync(pluginPath).isDirectory() + }) + + return plugins + .map((plugin) => { + try { + const manifestPath = path.join(pluginsDir, plugin, 'manifest.yml') + const manifest = yaml.load(fs.readFileSync(manifestPath, 'utf8')) as ManifestT + return { + name: plugin, + label: manifest.label, + version: manifest.version, + author: manifest.author + } + } catch (error) { + console.warn(`Failed to load plugin ${plugin}:`, error) + return null + } + }) + .filter((plugin): plugin is PluginT => plugin !== null) +} + /** * Sets the directory where plugins are stored. * @param newPath the path to the plugins directory to set. @@ -335,3 +375,30 @@ export const setHotkey = (type: string, hotkey: string): Promise => { }) }) } + +export const setDisabledPlugins = (pluginName: string, isDisabled: boolean): Promise => { + return new Promise((resolve, reject) => { + getDisabledPlugins() + .then((disabledPlugins) => { + const updatedPlugins = isDisabled + ? [...disabledPlugins, pluginName] + : disabledPlugins.filter((n) => n !== pluginName) + storage.set('disabledPlugins', updatedPlugins, (error) => { + if (error) reject(error) + else resolve() + }) + }) + .catch(reject) + }) +} + +export const getDisabledPlugins = (): Promise => { + return new Promise((resolve) => { + storage.get('disabledPlugins', (error, data) => { + if (error) throw error + // Ensure we always return an array, even if data is null, undefined, or not an array + const disabledPlugins = Array.isArray(data) ? data : [] + resolve(disabledPlugins) + }) + }) +} diff --git a/src/main/index.ts b/src/main/index.ts index 85a1a99..53e1e06 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -7,14 +7,17 @@ import icon from '../../resources/icon.png?asset' import { choosePluginsDir, getCommands, + getDisabledPlugins, getHotkeys, getPluginActions, getPluginsDir, + getPlugins, listInstalledApplications, openApplication, openExternal, runCommand, runPluginAction, + setDisabledPlugins, setHotkey } from './handlers' import { setupAutoUpdater } from './autoUpdater' @@ -156,6 +159,18 @@ if (!gotTheLock) { return getPluginsDir() }) + ipcMain.handle('get-plugins', async () => { + return getPlugins() + }) + + ipcMain.handle('get-disabled-plugins', async () => { + return getDisabledPlugins() + }) + + ipcMain.handle('set-disabled-plugins', async (_, pluginName, isDisabled) => { + return setDisabledPlugins(pluginName, isDisabled) + }) + ipcMain.handle('set-hotkey', async (_, type, hotkey) => { return setHotkey(type, hotkey) }) diff --git a/src/preload/index.ts b/src/preload/index.ts index 9471961..fa22e4d 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -34,12 +34,21 @@ if (process.contextIsolated) { getPluginsDir: () => { return ipcRenderer.invoke('get-plugins-dir') }, + getPlugins: () => { + return ipcRenderer.invoke('get-plugins') + }, getHotkeys: () => { return ipcRenderer.invoke('get-hotkeys') }, setHotkey: (type, hotkey) => { return ipcRenderer.invoke('set-hotkey', type, hotkey) }, + setDisabledPlugins: (pluginName, isDisabled) => { + return ipcRenderer.invoke('set-disabled-plugins', pluginName, isDisabled) + }, + getDisabledPlugins: () => { + return ipcRenderer.invoke('get-disabled-plugins') + }, showMainWindow: () => { return ipcRenderer.send('show-main-window') }, diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 5359d9d..3670480 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -14,9 +14,9 @@ const App = () => { const [selectedCommand, setSelectedCommand] = useState(null) const [commandSearch, setCommandSearch] = useState('') const commandListRef = useRef(null) + const [currentBangName, setCurrentBangName] = useState(null) useScrollToTop(commandListRef, [commandSearch]) - const handleInputKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape') { e.preventDefault() @@ -44,7 +44,11 @@ const App = () => { onKeyDown={handleInputKeyDown} placeholder="Search commands..." /> - + {currentBangName && ( + + {currentBangName} + + )} @@ -52,7 +56,7 @@ const App = () => { - +