mirror of
https://github.com/ByteAtATime/raycast-linux.git
synced 2025-09-03 12:47:23 +00:00
refactor(command-palette): extract logic into state and action bar
This commit refactors the main `CommandPalette.svelte` component by extracting its complex logic into hooks. Additionally, the footer UI has been moved into a new `ActionBar.svelte` component.
This commit is contained in:
parent
398ed86c40
commit
d3e615de7e
6 changed files with 551 additions and 448 deletions
|
@ -1,7 +1,7 @@
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { frecencyStore } from './frecency.svelte';
|
import { frecencyStore } from './frecency.svelte';
|
||||||
|
|
||||||
type App = { name: string; comment?: string; exec: string; icon_path?: string };
|
export type App = { name: string; comment?: string; exec: string; icon_path?: string };
|
||||||
|
|
||||||
class AppsStore {
|
class AppsStore {
|
||||||
rawApps = $state<App[]>([]);
|
rawApps = $state<App[]>([]);
|
||||||
|
|
243
src/lib/command-palette.svelte.ts
Normal file
243
src/lib/command-palette.svelte.ts
Normal file
|
@ -0,0 +1,243 @@
|
||||||
|
import type { PluginInfo } from '@raycast-linux/protocol';
|
||||||
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
|
import Fuse from 'fuse.js';
|
||||||
|
import { create, all } from 'mathjs';
|
||||||
|
import { writeText } from '@tauri-apps/plugin-clipboard-manager';
|
||||||
|
import type { Quicklink } from '$lib/quicklinks.svelte';
|
||||||
|
import { frecencyStore } from '$lib/frecency.svelte';
|
||||||
|
import { viewManager } from './viewManager.svelte';
|
||||||
|
import type { App } from './apps.svelte';
|
||||||
|
|
||||||
|
export type UnifiedItem = {
|
||||||
|
type: 'calculator' | 'plugin' | 'app' | 'quicklink';
|
||||||
|
id: string;
|
||||||
|
data: any;
|
||||||
|
score: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type UseCommandPaletteItemsArgs = {
|
||||||
|
searchText: () => string;
|
||||||
|
plugins: () => PluginInfo[];
|
||||||
|
installedApps: () => App[];
|
||||||
|
quicklinks: () => Quicklink[];
|
||||||
|
frecencyData: () => { itemId: string; useCount: number; lastUsedAt: number }[];
|
||||||
|
selectedQuicklinkForArgument: () => Quicklink | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const math = create(all);
|
||||||
|
|
||||||
|
export function useCommandPaletteItems({
|
||||||
|
searchText,
|
||||||
|
plugins,
|
||||||
|
installedApps,
|
||||||
|
quicklinks,
|
||||||
|
frecencyData,
|
||||||
|
selectedQuicklinkForArgument
|
||||||
|
}: UseCommandPaletteItemsArgs) {
|
||||||
|
const allSearchableItems = $derived.by(() => {
|
||||||
|
const items: { type: 'plugin' | 'app' | 'quicklink'; id: string; data: any }[] = [];
|
||||||
|
items.push(...plugins().map((p) => ({ type: 'plugin', id: p.pluginPath, data: p }) as const));
|
||||||
|
items.push(...installedApps().map((a) => ({ type: 'app', id: a.exec, data: a }) as const));
|
||||||
|
items.push(
|
||||||
|
...quicklinks().map((q) => ({ type: 'quicklink', id: `quicklink-${q.id}`, data: q }) as const)
|
||||||
|
);
|
||||||
|
return items;
|
||||||
|
});
|
||||||
|
|
||||||
|
const fuse = $derived(
|
||||||
|
new Fuse(allSearchableItems, {
|
||||||
|
keys: [
|
||||||
|
'data.title',
|
||||||
|
'data.pluginTitle',
|
||||||
|
'data.description',
|
||||||
|
'data.name',
|
||||||
|
'data.comment',
|
||||||
|
'data.link'
|
||||||
|
],
|
||||||
|
threshold: 0.4,
|
||||||
|
includeScore: true
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const calculatorResult = $derived.by(() => {
|
||||||
|
const term = searchText();
|
||||||
|
if (!term.trim() || selectedQuicklinkForArgument()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = math.evaluate(term.trim());
|
||||||
|
if (typeof result === 'function' || typeof result === 'undefined') return null;
|
||||||
|
const resultString = math.format(result, { precision: 14 });
|
||||||
|
if (resultString === term.trim()) return null;
|
||||||
|
return { value: resultString, type: math.typeOf(result) };
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const displayItems = $derived.by(() => {
|
||||||
|
let items: (UnifiedItem & { fuseScore?: number })[] = [];
|
||||||
|
const term = searchText();
|
||||||
|
|
||||||
|
if (term.trim()) {
|
||||||
|
items = fuse.search(term).map((result) => ({
|
||||||
|
...result.item,
|
||||||
|
score: 0,
|
||||||
|
fuseScore: result.score
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
items = allSearchableItems.map((item) => ({ ...item, score: 0, fuseScore: 1 }));
|
||||||
|
}
|
||||||
|
|
||||||
|
const frecencyMap = new Map(frecencyData().map((item) => [item.itemId, item]));
|
||||||
|
const now = Date.now() / 1000;
|
||||||
|
const gravity = 1.8;
|
||||||
|
|
||||||
|
items.forEach((item) => {
|
||||||
|
const frecency = frecencyMap.get(item.id);
|
||||||
|
let frecencyScore = 0;
|
||||||
|
if (frecency) {
|
||||||
|
const ageInHours = Math.max(1, (now - frecency.lastUsedAt) / 3600);
|
||||||
|
frecencyScore = (frecency.useCount * 1000) / Math.pow(ageInHours + 2, gravity);
|
||||||
|
}
|
||||||
|
const textScore = item.fuseScore !== undefined ? 1 - item.fuseScore * 100 : 0;
|
||||||
|
item.score = frecencyScore + textScore;
|
||||||
|
});
|
||||||
|
|
||||||
|
items.sort((a, b) => b.score - a.score);
|
||||||
|
|
||||||
|
const calcRes = calculatorResult;
|
||||||
|
if (calcRes) {
|
||||||
|
items.unshift({
|
||||||
|
type: 'calculator',
|
||||||
|
id: 'calculator',
|
||||||
|
data: {
|
||||||
|
value: term,
|
||||||
|
result: calcRes.value,
|
||||||
|
resultType: calcRes.type
|
||||||
|
},
|
||||||
|
score: 9999
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
});
|
||||||
|
|
||||||
|
return { displayItems };
|
||||||
|
}
|
||||||
|
|
||||||
|
type UseCommandPaletteActionsArgs = {
|
||||||
|
selectedItem: () => UnifiedItem | undefined;
|
||||||
|
onRunPlugin: (plugin: PluginInfo) => void;
|
||||||
|
resetState: () => void;
|
||||||
|
focusArgumentInput: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useCommandPaletteActions({
|
||||||
|
selectedItem,
|
||||||
|
onRunPlugin,
|
||||||
|
resetState,
|
||||||
|
focusArgumentInput
|
||||||
|
}: UseCommandPaletteActionsArgs) {
|
||||||
|
async function executeQuicklink(quicklink: Quicklink, argument?: string) {
|
||||||
|
const finalLink = argument
|
||||||
|
? quicklink.link.replace(/\{argument\}/g, encodeURIComponent(argument))
|
||||||
|
: quicklink.link.replace(/\{argument\}/g, '');
|
||||||
|
await invoke('execute_quicklink', {
|
||||||
|
link: finalLink,
|
||||||
|
application: quicklink.application
|
||||||
|
});
|
||||||
|
resetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleEnter() {
|
||||||
|
const item = selectedItem();
|
||||||
|
if (!item) return;
|
||||||
|
|
||||||
|
await frecencyStore.recordUsage(item.id);
|
||||||
|
|
||||||
|
switch (item.type) {
|
||||||
|
case 'calculator': {
|
||||||
|
writeText(item.data.result);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'plugin': {
|
||||||
|
onRunPlugin(item.data as PluginInfo);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'app': {
|
||||||
|
if (item.data.exec) {
|
||||||
|
invoke('launch_app', { exec: item.data.exec }).catch(console.error);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'quicklink': {
|
||||||
|
const quicklink = item.data as Quicklink;
|
||||||
|
if (quicklink.link.includes('{argument}')) {
|
||||||
|
focusArgumentInput();
|
||||||
|
} else {
|
||||||
|
executeQuicklink(quicklink);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleResetRanking() {
|
||||||
|
const item = selectedItem();
|
||||||
|
if (item) {
|
||||||
|
await frecencyStore.deleteEntry(item.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCopyDeeplink() {
|
||||||
|
const item = selectedItem();
|
||||||
|
if (item?.type !== 'plugin') return;
|
||||||
|
const plugin = item.data as PluginInfo;
|
||||||
|
const authorOrOwner =
|
||||||
|
plugin.owner === 'raycast'
|
||||||
|
? 'raycast'
|
||||||
|
: typeof plugin.author === 'string'
|
||||||
|
? plugin.author
|
||||||
|
: (plugin.author?.name ?? 'unknown');
|
||||||
|
|
||||||
|
const deeplink = `raycast://extensions/${authorOrOwner}/${plugin.pluginName}/${plugin.commandName}`;
|
||||||
|
writeText(deeplink);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleConfigureCommand() {
|
||||||
|
const item = selectedItem();
|
||||||
|
if (item?.type !== 'plugin') return;
|
||||||
|
viewManager.showSettings(item.data.pluginName);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCopyAppName() {
|
||||||
|
const item = selectedItem();
|
||||||
|
if (item?.type !== 'app') return;
|
||||||
|
writeText(item.data.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCopyAppPath() {
|
||||||
|
const item = selectedItem();
|
||||||
|
if (item?.type !== 'app') return;
|
||||||
|
writeText(item.data.exec);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleHideApp() {
|
||||||
|
const item = selectedItem();
|
||||||
|
if (item?.type !== 'app') return;
|
||||||
|
await frecencyStore.hideItem(item.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
executeQuicklink,
|
||||||
|
handleEnter,
|
||||||
|
handleResetRanking,
|
||||||
|
handleCopyDeeplink,
|
||||||
|
handleConfigureCommand,
|
||||||
|
handleCopyAppName,
|
||||||
|
handleCopyAppPath,
|
||||||
|
handleHideApp
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,446 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import type { PluginInfo } from '@raycast-linux/protocol';
|
|
||||||
import { Input } from '$lib/components/ui/input';
|
|
||||||
import Calculator from '$lib/components/Calculator.svelte';
|
|
||||||
import BaseList from '$lib/components/BaseList.svelte';
|
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
|
||||||
import Fuse from 'fuse.js';
|
|
||||||
import ListItemBase from './nodes/shared/ListItemBase.svelte';
|
|
||||||
import path from 'path';
|
|
||||||
import { create, all } from 'mathjs';
|
|
||||||
import { writeText } from '@tauri-apps/plugin-clipboard-manager';
|
|
||||||
import { tick } from 'svelte';
|
|
||||||
import { quicklinksStore, type Quicklink } from '$lib/quicklinks.svelte';
|
|
||||||
import { appsStore } from '$lib/apps.svelte';
|
|
||||||
import { frecencyStore } from '$lib/frecency.svelte';
|
|
||||||
import ActionBar from './nodes/shared/ActionBar.svelte';
|
|
||||||
import { Button } from './ui/button';
|
|
||||||
import { Kbd } from './ui/kbd';
|
|
||||||
import ActionMenu from './nodes/shared/ActionMenu.svelte';
|
|
||||||
import * as DropdownMenu from './ui/dropdown-menu';
|
|
||||||
import { Separator } from './ui/separator';
|
|
||||||
import { shortcutToText } from '$lib/renderKey';
|
|
||||||
import { viewManager } from '$lib/viewManager.svelte';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
plugins: PluginInfo[];
|
|
||||||
onRunPlugin: (plugin: PluginInfo) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
type UnifiedItem = {
|
|
||||||
type: 'calculator' | 'plugin' | 'app' | 'quicklink';
|
|
||||||
id: string;
|
|
||||||
data: any;
|
|
||||||
score: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
let { plugins, onRunPlugin }: Props = $props();
|
|
||||||
|
|
||||||
const { apps: installedApps } = $derived(appsStore);
|
|
||||||
const { quicklinks } = $derived(quicklinksStore);
|
|
||||||
const { data: frecencyData } = $derived(frecencyStore);
|
|
||||||
|
|
||||||
let searchText = $state('');
|
|
||||||
let quicklinkArgument = $state('');
|
|
||||||
let selectedIndex = $state(0);
|
|
||||||
let listElement: HTMLElement | null = $state(null);
|
|
||||||
let searchInputEl: HTMLInputElement | null = $state(null);
|
|
||||||
let argumentInputEl: HTMLInputElement | null = $state(null);
|
|
||||||
let selectedQuicklinkForArgument: Quicklink | null = $state(null);
|
|
||||||
|
|
||||||
const math = create(all);
|
|
||||||
|
|
||||||
const frecencyMap = $derived(new Map(frecencyData.map((item) => [item.itemId, item])));
|
|
||||||
|
|
||||||
const allSearchableItems = $derived.by(() => {
|
|
||||||
const items = [];
|
|
||||||
items.push(...plugins.map((p) => ({ type: 'plugin', id: p.pluginPath, data: p }) as const));
|
|
||||||
items.push(...installedApps.map((a) => ({ type: 'app', id: a.exec, data: a }) as const));
|
|
||||||
items.push(
|
|
||||||
...quicklinks.map((q) => ({ type: 'quicklink', id: `quicklink-${q.id}`, data: q }) as const)
|
|
||||||
);
|
|
||||||
return items;
|
|
||||||
});
|
|
||||||
|
|
||||||
const fuse = $derived(
|
|
||||||
new Fuse(allSearchableItems, {
|
|
||||||
keys: [
|
|
||||||
'data.title',
|
|
||||||
'data.pluginTitle',
|
|
||||||
'data.description',
|
|
||||||
'data.name',
|
|
||||||
'data.comment',
|
|
||||||
'data.link'
|
|
||||||
],
|
|
||||||
threshold: 0.4,
|
|
||||||
includeScore: true
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const calculatorResult = $derived.by(() => {
|
|
||||||
if (!searchText.trim() || selectedQuicklinkForArgument) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = math.evaluate(searchText.trim());
|
|
||||||
if (typeof result === 'function' || typeof result === 'undefined') return null;
|
|
||||||
let resultString = math.format(result, { precision: 14 });
|
|
||||||
if (resultString === searchText.trim()) return null;
|
|
||||||
return { value: resultString, type: math.typeOf(result) };
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const displayItems = $derived.by(() => {
|
|
||||||
let items: (UnifiedItem & { fuseScore?: number })[] = [];
|
|
||||||
|
|
||||||
if (searchText.trim()) {
|
|
||||||
items = fuse.search(searchText).map((result) => ({
|
|
||||||
...result.item,
|
|
||||||
score: 0,
|
|
||||||
fuseScore: result.score
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
items = allSearchableItems.map((item) => ({ ...item, score: 0, fuseScore: 1 }));
|
|
||||||
}
|
|
||||||
|
|
||||||
const now = Date.now() / 1000;
|
|
||||||
const gravity = 1.8;
|
|
||||||
|
|
||||||
items.forEach((item) => {
|
|
||||||
const frecency = frecencyMap.get(item.id);
|
|
||||||
let frecencyScore = 0;
|
|
||||||
if (frecency) {
|
|
||||||
const ageInHours = Math.max(1, (now - frecency.lastUsedAt) / 3600);
|
|
||||||
frecencyScore = (frecency.useCount * 1000) / Math.pow(ageInHours + 2, gravity);
|
|
||||||
}
|
|
||||||
|
|
||||||
const textScore = item.fuseScore !== undefined ? 1 - item.fuseScore * 100 : 0;
|
|
||||||
item.score = frecencyScore + textScore;
|
|
||||||
});
|
|
||||||
|
|
||||||
items.sort((a, b) => b.score - a.score);
|
|
||||||
|
|
||||||
if (calculatorResult) {
|
|
||||||
items.unshift({
|
|
||||||
type: 'calculator',
|
|
||||||
id: 'calculator',
|
|
||||||
data: {
|
|
||||||
value: searchText,
|
|
||||||
result: calculatorResult.value,
|
|
||||||
resultType: calculatorResult.type
|
|
||||||
},
|
|
||||||
score: 9999
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return items;
|
|
||||||
});
|
|
||||||
|
|
||||||
const selectedItem = $derived(displayItems[selectedIndex]);
|
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
const item = displayItems[selectedIndex];
|
|
||||||
if (item?.type === 'quicklink' && item.data.link.includes('{argument}')) {
|
|
||||||
selectedQuicklinkForArgument = item.data;
|
|
||||||
} else {
|
|
||||||
selectedQuicklinkForArgument = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function resetState() {
|
|
||||||
searchText = '';
|
|
||||||
quicklinkArgument = '';
|
|
||||||
selectedIndex = 0;
|
|
||||||
selectedQuicklinkForArgument = null;
|
|
||||||
tick().then(() => searchInputEl?.focus());
|
|
||||||
}
|
|
||||||
|
|
||||||
async function executeQuicklink(quicklink: Quicklink, argument?: string) {
|
|
||||||
const finalLink = argument
|
|
||||||
? quicklink.link.replace(/\{argument\}/g, encodeURIComponent(argument))
|
|
||||||
: quicklink.link.replace(/\{argument\}/g, '');
|
|
||||||
await invoke('execute_quicklink', {
|
|
||||||
link: finalLink,
|
|
||||||
application: quicklink.application
|
|
||||||
});
|
|
||||||
resetState();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleEnter() {
|
|
||||||
if (!selectedItem) return;
|
|
||||||
const item = selectedItem;
|
|
||||||
await frecencyStore.recordUsage(item.id);
|
|
||||||
|
|
||||||
switch (item.type) {
|
|
||||||
case 'calculator':
|
|
||||||
writeText(item.data.result);
|
|
||||||
break;
|
|
||||||
case 'plugin':
|
|
||||||
onRunPlugin(item.data as PluginInfo);
|
|
||||||
break;
|
|
||||||
case 'app':
|
|
||||||
if (item.data.exec) {
|
|
||||||
invoke('launch_app', { exec: item.data.exec }).catch(console.error);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'quicklink':
|
|
||||||
const quicklink = item.data as Quicklink;
|
|
||||||
if (quicklink.link.includes('{argument}')) {
|
|
||||||
await tick();
|
|
||||||
argumentInputEl?.focus();
|
|
||||||
} else {
|
|
||||||
executeQuicklink(quicklink);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleArgumentKeydown(e: KeyboardEvent) {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
e.preventDefault();
|
|
||||||
if (selectedQuicklinkForArgument) {
|
|
||||||
await executeQuicklink(selectedQuicklinkForArgument, quicklinkArgument);
|
|
||||||
}
|
|
||||||
} else if (e.key === 'Escape' || (e.key === 'Backspace' && quicklinkArgument === '')) {
|
|
||||||
e.preventDefault();
|
|
||||||
quicklinkArgument = '';
|
|
||||||
await tick();
|
|
||||||
searchInputEl?.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleResetRanking() {
|
|
||||||
if (selectedItem) {
|
|
||||||
const itemToReset = selectedItem;
|
|
||||||
await frecencyStore.deleteEntry(itemToReset.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleCopyDeeplink() {
|
|
||||||
if (selectedItem?.type !== 'plugin') return;
|
|
||||||
const plugin = selectedItem.data as PluginInfo;
|
|
||||||
const authorOrOwner =
|
|
||||||
plugin.owner === 'raycast'
|
|
||||||
? 'raycast'
|
|
||||||
: typeof plugin.author === 'string'
|
|
||||||
? plugin.author
|
|
||||||
: (plugin.author?.name ?? 'unknown');
|
|
||||||
|
|
||||||
const deeplink = `raycast://extensions/${authorOrOwner}/${plugin.pluginName}/${plugin.commandName}`;
|
|
||||||
writeText(deeplink);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleConfigureCommand() {
|
|
||||||
if (selectedItem?.type !== 'plugin') return;
|
|
||||||
const plugin = selectedItem.data as PluginInfo;
|
|
||||||
viewManager.showSettings(plugin.pluginName);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleCopyAppName() {
|
|
||||||
if (selectedItem?.type !== 'app') return;
|
|
||||||
writeText(selectedItem.data.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleCopyAppPath() {
|
|
||||||
if (selectedItem?.type !== 'app') return;
|
|
||||||
writeText(selectedItem.data.exec);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleHideApp() {
|
|
||||||
if (selectedItem?.type !== 'app') return;
|
|
||||||
const itemToHide = selectedItem;
|
|
||||||
await frecencyStore.hideItem(itemToHide.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleKeyDown(e: KeyboardEvent) {
|
|
||||||
if (!selectedItem) return;
|
|
||||||
|
|
||||||
const keyMap: Record<string, (() => void) | (() => Promise<void>) | undefined> = {
|
|
||||||
'C-S-c': selectedItem.type === 'plugin' ? handleCopyDeeplink : undefined,
|
|
||||||
'C-S-,': selectedItem.type === 'plugin' ? handleConfigureCommand : undefined,
|
|
||||||
'C-.': selectedItem.type === 'app' ? handleCopyAppName : undefined,
|
|
||||||
'C-S-.': selectedItem.type === 'app' ? handleCopyAppPath : undefined,
|
|
||||||
'C-h': selectedItem.type === 'app' ? handleHideApp : undefined
|
|
||||||
};
|
|
||||||
|
|
||||||
const shortcut = `${e.metaKey ? 'M-' : ''}${e.ctrlKey ? 'C-' : ''}${e.shiftKey ? 'S-' : ''}${e.key.toLowerCase()}`;
|
|
||||||
const action =
|
|
||||||
keyMap[shortcut.replace('meta', 'M').replace('control', 'C').replace('shift', 'S')];
|
|
||||||
|
|
||||||
if (action) {
|
|
||||||
e.preventDefault();
|
|
||||||
await action();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svelte:window onkeydown={handleKeyDown} />
|
|
||||||
|
|
||||||
<main class="bg-background text-foreground flex h-screen flex-col">
|
|
||||||
<header class="flex h-12 shrink-0 items-center border-b px-2">
|
|
||||||
<div class="relative flex w-full items-center">
|
|
||||||
<Input
|
|
||||||
class="w-full rounded-none border-none !bg-transparent pr-0 text-base"
|
|
||||||
placeholder={selectedQuicklinkForArgument
|
|
||||||
? selectedQuicklinkForArgument.name
|
|
||||||
: 'Search for extensions and commands...'}
|
|
||||||
bind:value={searchText}
|
|
||||||
bind:ref={searchInputEl}
|
|
||||||
autofocus
|
|
||||||
/>
|
|
||||||
|
|
||||||
{#if selectedQuicklinkForArgument}
|
|
||||||
<div class="pointer-events-none absolute top-0 left-0 flex h-full w-full items-center">
|
|
||||||
<span class="whitespace-pre text-transparent"
|
|
||||||
>{searchText || selectedQuicklinkForArgument.name}</span
|
|
||||||
>
|
|
||||||
<span class="w-2"></span>
|
|
||||||
<div class="pointer-events-auto">
|
|
||||||
<div class="inline-grid items-center">
|
|
||||||
<span
|
|
||||||
class="invisible col-start-1 row-start-1 px-3 text-base whitespace-pre md:text-sm"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
{quicklinkArgument || 'Query'}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<Input
|
|
||||||
class="col-start-1 row-start-1 h-7 w-full"
|
|
||||||
placeholder="Query"
|
|
||||||
bind:value={quicklinkArgument}
|
|
||||||
bind:ref={argumentInputEl}
|
|
||||||
onkeydown={handleArgumentKeydown}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="grow overflow-y-auto">
|
|
||||||
<BaseList
|
|
||||||
items={displayItems.map((item) => ({ ...item, itemType: 'item' }))}
|
|
||||||
onenter={handleEnter}
|
|
||||||
bind:selectedIndex
|
|
||||||
bind:listElement
|
|
||||||
>
|
|
||||||
{#snippet itemSnippet({ item, isSelected, onclick })}
|
|
||||||
{#if item.type === 'calculator'}
|
|
||||||
<Calculator
|
|
||||||
searchText={item.data.value}
|
|
||||||
mathResult={item.data.result}
|
|
||||||
mathResultType={item.data.resultType}
|
|
||||||
{isSelected}
|
|
||||||
onSelect={onclick}
|
|
||||||
/>
|
|
||||||
{:else if item.type === 'plugin'}
|
|
||||||
{@const assetsPath = path.dirname(item.data.pluginPath) + '/assets'}
|
|
||||||
<ListItemBase
|
|
||||||
title={item.data.title}
|
|
||||||
subtitle={item.data.pluginTitle}
|
|
||||||
icon={item.data.icon || 'app-window-16'}
|
|
||||||
{assetsPath}
|
|
||||||
{isSelected}
|
|
||||||
{onclick}
|
|
||||||
>
|
|
||||||
{#snippet accessories()}
|
|
||||||
<span class="text-muted-foreground ml-auto text-xs whitespace-nowrap"> Command </span>
|
|
||||||
{/snippet}
|
|
||||||
</ListItemBase>
|
|
||||||
{:else if item.type === 'app'}
|
|
||||||
<ListItemBase
|
|
||||||
title={item.data.name}
|
|
||||||
subtitle={item.data.comment}
|
|
||||||
icon={item.data.icon_path ?? 'app-window-16'}
|
|
||||||
{isSelected}
|
|
||||||
{onclick}
|
|
||||||
>
|
|
||||||
{#snippet accessories()}
|
|
||||||
<span class="text-muted-foreground ml-auto text-xs whitespace-nowrap">
|
|
||||||
Application
|
|
||||||
</span>
|
|
||||||
{/snippet}
|
|
||||||
</ListItemBase>
|
|
||||||
{:else if item.type === 'quicklink'}
|
|
||||||
<ListItemBase
|
|
||||||
title={item.data.name}
|
|
||||||
subtitle={item.data.link.replace(/\{argument\}/g, '...')}
|
|
||||||
icon={item.data.icon ?? 'link-16'}
|
|
||||||
{isSelected}
|
|
||||||
{onclick}
|
|
||||||
>
|
|
||||||
{#snippet accessories()}
|
|
||||||
<span class="text-muted-foreground ml-auto text-xs whitespace-nowrap">
|
|
||||||
Quicklink
|
|
||||||
</span>
|
|
||||||
{/snippet}
|
|
||||||
</ListItemBase>
|
|
||||||
{/if}
|
|
||||||
{/snippet}
|
|
||||||
</BaseList>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if selectedItem}
|
|
||||||
<ActionBar>
|
|
||||||
{#snippet primaryAction({ props })}
|
|
||||||
{@const primaryActionText =
|
|
||||||
selectedItem.type === 'app'
|
|
||||||
? 'Open Application'
|
|
||||||
: selectedItem.type === 'quicklink'
|
|
||||||
? 'Open Quicklink'
|
|
||||||
: 'Open Command'}
|
|
||||||
<Button {...props} onclick={handleEnter}>
|
|
||||||
{primaryActionText}
|
|
||||||
<Kbd>⏎</Kbd>
|
|
||||||
</Button>
|
|
||||||
{/snippet}
|
|
||||||
{#snippet actions()}
|
|
||||||
<ActionMenu>
|
|
||||||
{#if selectedItem.type === 'plugin'}
|
|
||||||
<DropdownMenu.Item onclick={handleResetRanking}>Reset Ranking</DropdownMenu.Item>
|
|
||||||
<DropdownMenu.Separator />
|
|
||||||
<DropdownMenu.Item onclick={handleCopyDeeplink}>
|
|
||||||
Copy Deeplink
|
|
||||||
<DropdownMenu.Shortcut>
|
|
||||||
{shortcutToText({ key: 'c', modifiers: ['ctrl', 'shift'] })}
|
|
||||||
</DropdownMenu.Shortcut>
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
<DropdownMenu.Separator />
|
|
||||||
<DropdownMenu.Item onclick={handleConfigureCommand}>
|
|
||||||
Configure Command
|
|
||||||
<DropdownMenu.Shortcut>
|
|
||||||
{shortcutToText({ key: ',', modifiers: ['ctrl', 'shift'] })}
|
|
||||||
</DropdownMenu.Shortcut>
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
{:else if selectedItem.type === 'app'}
|
|
||||||
<DropdownMenu.Item onclick={handleResetRanking}>Reset Ranking</DropdownMenu.Item>
|
|
||||||
<DropdownMenu.Separator />
|
|
||||||
<DropdownMenu.Item onclick={handleCopyAppName}>
|
|
||||||
Copy Name
|
|
||||||
<DropdownMenu.Shortcut>
|
|
||||||
{shortcutToText({ key: '.', modifiers: ['ctrl'] })}
|
|
||||||
</DropdownMenu.Shortcut>
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
<DropdownMenu.Item onclick={handleCopyAppPath}>
|
|
||||||
Copy Path
|
|
||||||
<DropdownMenu.Shortcut>
|
|
||||||
{shortcutToText({ key: '.', modifiers: ['ctrl', 'shift'] })}
|
|
||||||
</DropdownMenu.Shortcut>
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
<DropdownMenu.Separator />
|
|
||||||
<DropdownMenu.Item onclick={handleHideApp}>
|
|
||||||
Hide Application
|
|
||||||
<DropdownMenu.Shortcut>
|
|
||||||
{shortcutToText({ key: 'h', modifiers: ['ctrl'] })}
|
|
||||||
</DropdownMenu.Shortcut>
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
{/if}
|
|
||||||
</ActionMenu>
|
|
||||||
{/snippet}
|
|
||||||
</ActionBar>
|
|
||||||
{/if}
|
|
||||||
</main>
|
|
88
src/lib/components/command-palette/ActionBar.svelte
Normal file
88
src/lib/components/command-palette/ActionBar.svelte
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { UnifiedItem } from '$lib/command-palette.svelte';
|
||||||
|
import ActionBar from '$lib/components/nodes/shared/ActionBar.svelte';
|
||||||
|
import { Button } from '$lib/components/ui/button';
|
||||||
|
import { Kbd } from '$lib/components/ui/kbd';
|
||||||
|
import ActionMenu from '$lib/components/nodes/shared/ActionMenu.svelte';
|
||||||
|
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||||
|
import { shortcutToText } from '$lib/renderKey';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
selectedItem: UnifiedItem | undefined;
|
||||||
|
actions: {
|
||||||
|
handleEnter: () => Promise<void>;
|
||||||
|
handleResetRanking: () => Promise<void>;
|
||||||
|
handleCopyDeeplink: () => void;
|
||||||
|
handleConfigureCommand: () => void;
|
||||||
|
handleCopyAppName: () => void;
|
||||||
|
handleCopyAppPath: () => void;
|
||||||
|
handleHideApp: () => Promise<void>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
let { selectedItem, actions: barActions }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if selectedItem}
|
||||||
|
<ActionBar>
|
||||||
|
{#snippet primaryAction({ props })}
|
||||||
|
{@const primaryActionText =
|
||||||
|
selectedItem.type === 'app'
|
||||||
|
? 'Open Application'
|
||||||
|
: selectedItem.type === 'quicklink'
|
||||||
|
? 'Open Quicklink'
|
||||||
|
: 'Open Command'}
|
||||||
|
<Button {...props} onclick={barActions?.handleEnter}>
|
||||||
|
{primaryActionText}
|
||||||
|
<Kbd>⏎</Kbd>
|
||||||
|
</Button>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet actions()}
|
||||||
|
<ActionMenu>
|
||||||
|
{#if selectedItem.type === 'plugin'}
|
||||||
|
<DropdownMenu.Item onclick={barActions.handleResetRanking}
|
||||||
|
>Reset Ranking</DropdownMenu.Item
|
||||||
|
>
|
||||||
|
<DropdownMenu.Separator />
|
||||||
|
<DropdownMenu.Item onclick={barActions.handleCopyDeeplink}>
|
||||||
|
Copy Deeplink
|
||||||
|
<DropdownMenu.Shortcut>
|
||||||
|
{shortcutToText({ key: 'c', modifiers: ['ctrl', 'shift'] })}
|
||||||
|
</DropdownMenu.Shortcut>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
<DropdownMenu.Separator />
|
||||||
|
<DropdownMenu.Item onclick={barActions.handleConfigureCommand}>
|
||||||
|
Configure Command
|
||||||
|
<DropdownMenu.Shortcut>
|
||||||
|
{shortcutToText({ key: ',', modifiers: ['ctrl', 'shift'] })}
|
||||||
|
</DropdownMenu.Shortcut>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
{:else if selectedItem.type === 'app'}
|
||||||
|
<DropdownMenu.Item onclick={barActions.handleResetRanking}
|
||||||
|
>Reset Ranking</DropdownMenu.Item
|
||||||
|
>
|
||||||
|
<DropdownMenu.Separator />
|
||||||
|
<DropdownMenu.Item onclick={barActions.handleCopyAppName}>
|
||||||
|
Copy Name
|
||||||
|
<DropdownMenu.Shortcut>
|
||||||
|
{shortcutToText({ key: '.', modifiers: ['ctrl'] })}
|
||||||
|
</DropdownMenu.Shortcut>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
<DropdownMenu.Item onclick={barActions.handleCopyAppPath}>
|
||||||
|
Copy Path
|
||||||
|
<DropdownMenu.Shortcut>
|
||||||
|
{shortcutToText({ key: '.', modifiers: ['ctrl', 'shift'] })}
|
||||||
|
</DropdownMenu.Shortcut>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
<DropdownMenu.Separator />
|
||||||
|
<DropdownMenu.Item onclick={barActions.handleHideApp}>
|
||||||
|
Hide Application
|
||||||
|
<DropdownMenu.Shortcut>
|
||||||
|
{shortcutToText({ key: 'h', modifiers: ['ctrl'] })}
|
||||||
|
</DropdownMenu.Shortcut>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
{/if}
|
||||||
|
</ActionMenu>
|
||||||
|
{/snippet}
|
||||||
|
</ActionBar>
|
||||||
|
{/if}
|
218
src/lib/components/command-palette/CommandPalette.svelte
Normal file
218
src/lib/components/command-palette/CommandPalette.svelte
Normal file
|
@ -0,0 +1,218 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { PluginInfo } from '@raycast-linux/protocol';
|
||||||
|
import { Input } from '$lib/components/ui/input';
|
||||||
|
import Calculator from '$lib/components/Calculator.svelte';
|
||||||
|
import BaseList from '$lib/components/BaseList.svelte';
|
||||||
|
import ListItemBase from '../nodes/shared/ListItemBase.svelte';
|
||||||
|
import path from 'path';
|
||||||
|
import { tick } from 'svelte';
|
||||||
|
import type { Quicklink } from '$lib/quicklinks.svelte';
|
||||||
|
import { appsStore } from '$lib/apps.svelte';
|
||||||
|
import { frecencyStore } from '$lib/frecency.svelte';
|
||||||
|
import { quicklinksStore } from '$lib/quicklinks.svelte';
|
||||||
|
import { useCommandPaletteItems, useCommandPaletteActions } from '$lib/command-palette.svelte';
|
||||||
|
import CommandPaletteActionBar from './ActionBar.svelte';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
plugins: PluginInfo[];
|
||||||
|
onRunPlugin: (plugin: PluginInfo) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
let { plugins, onRunPlugin }: Props = $props();
|
||||||
|
|
||||||
|
const { apps: installedApps } = $derived(appsStore);
|
||||||
|
const { quicklinks } = $derived(quicklinksStore);
|
||||||
|
const { data: frecencyData } = $derived(frecencyStore);
|
||||||
|
|
||||||
|
let searchText = $state('');
|
||||||
|
let quicklinkArgument = $state('');
|
||||||
|
let selectedIndex = $state(0);
|
||||||
|
let listElement: HTMLElement | null = $state(null);
|
||||||
|
let searchInputEl: HTMLInputElement | null = $state(null);
|
||||||
|
let argumentInputEl: HTMLInputElement | null = $state(null);
|
||||||
|
let selectedQuicklinkForArgument: Quicklink | null = $state(null);
|
||||||
|
|
||||||
|
const { displayItems } = useCommandPaletteItems({
|
||||||
|
searchText: () => searchText,
|
||||||
|
plugins: () => plugins,
|
||||||
|
installedApps: () => installedApps,
|
||||||
|
quicklinks: () => quicklinks,
|
||||||
|
frecencyData: () => frecencyData,
|
||||||
|
selectedQuicklinkForArgument: () => selectedQuicklinkForArgument
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedItem = $derived(displayItems[selectedIndex]);
|
||||||
|
|
||||||
|
function resetState() {
|
||||||
|
searchText = '';
|
||||||
|
quicklinkArgument = '';
|
||||||
|
selectedIndex = 0;
|
||||||
|
selectedQuicklinkForArgument = null;
|
||||||
|
tick().then(() => searchInputEl?.focus());
|
||||||
|
}
|
||||||
|
|
||||||
|
async function focusArgumentInput() {
|
||||||
|
await tick();
|
||||||
|
argumentInputEl?.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
const actions = useCommandPaletteActions({
|
||||||
|
selectedItem: () => selectedItem,
|
||||||
|
onRunPlugin,
|
||||||
|
resetState,
|
||||||
|
focusArgumentInput
|
||||||
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
const item = displayItems[selectedIndex];
|
||||||
|
if (item?.type === 'quicklink' && item.data.link.includes('{argument}')) {
|
||||||
|
selectedQuicklinkForArgument = item.data;
|
||||||
|
} else {
|
||||||
|
selectedQuicklinkForArgument = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function handleArgumentKeydown(e: KeyboardEvent) {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
if (selectedQuicklinkForArgument) {
|
||||||
|
await actions.executeQuicklink(selectedQuicklinkForArgument, quicklinkArgument);
|
||||||
|
}
|
||||||
|
} else if (e.key === 'Escape' || (e.key === 'Backspace' && quicklinkArgument === '')) {
|
||||||
|
e.preventDefault();
|
||||||
|
quicklinkArgument = '';
|
||||||
|
await tick();
|
||||||
|
searchInputEl?.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleKeyDown(e: KeyboardEvent) {
|
||||||
|
if (!selectedItem) return;
|
||||||
|
|
||||||
|
const keyMap = {
|
||||||
|
'C-S-c': selectedItem.type === 'plugin' ? actions.handleCopyDeeplink : undefined,
|
||||||
|
'C-S-,': selectedItem.type === 'plugin' ? actions.handleConfigureCommand : undefined,
|
||||||
|
'C-.': selectedItem.type === 'app' ? actions.handleCopyAppName : undefined,
|
||||||
|
'C-S-.': selectedItem.type === 'app' ? actions.handleCopyAppPath : undefined,
|
||||||
|
'C-h': selectedItem.type === 'app' ? actions.handleHideApp : undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
const shortcut = `${e.metaKey ? 'M-' : ''}${e.ctrlKey ? 'C-' : ''}${e.shiftKey ? 'S-' : ''}${e.key.toLowerCase()}`;
|
||||||
|
const action = keyMap[shortcut as keyof typeof keyMap];
|
||||||
|
|
||||||
|
if (action) {
|
||||||
|
e.preventDefault();
|
||||||
|
await action();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:window onkeydown={handleKeyDown} />
|
||||||
|
|
||||||
|
<main class="bg-background text-foreground flex h-screen flex-col">
|
||||||
|
<header class="flex h-12 shrink-0 items-center border-b px-2">
|
||||||
|
<div class="relative flex w-full items-center">
|
||||||
|
<Input
|
||||||
|
class="w-full rounded-none border-none !bg-transparent pr-0 text-base"
|
||||||
|
placeholder={selectedQuicklinkForArgument
|
||||||
|
? selectedQuicklinkForArgument.name
|
||||||
|
: 'Search for extensions and commands...'}
|
||||||
|
bind:value={searchText}
|
||||||
|
bind:ref={searchInputEl}
|
||||||
|
autofocus
|
||||||
|
/>
|
||||||
|
|
||||||
|
{#if selectedQuicklinkForArgument}
|
||||||
|
<div class="pointer-events-none absolute top-0 left-0 flex h-full w-full items-center">
|
||||||
|
<span class="whitespace-pre text-transparent"
|
||||||
|
>{searchText || selectedQuicklinkForArgument.name}</span
|
||||||
|
>
|
||||||
|
<span class="w-2"></span>
|
||||||
|
<div class="pointer-events-auto">
|
||||||
|
<div class="inline-grid items-center">
|
||||||
|
<span
|
||||||
|
class="invisible col-start-1 row-start-1 px-3 text-base whitespace-pre md:text-sm"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
{quicklinkArgument || 'Query'}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
class="col-start-1 row-start-1 h-7 w-full"
|
||||||
|
placeholder="Query"
|
||||||
|
bind:value={quicklinkArgument}
|
||||||
|
bind:ref={argumentInputEl}
|
||||||
|
onkeydown={handleArgumentKeydown}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="grow overflow-y-auto">
|
||||||
|
<BaseList
|
||||||
|
items={displayItems.map((item) => ({ ...item, itemType: 'item' }))}
|
||||||
|
onenter={actions.handleEnter}
|
||||||
|
bind:selectedIndex
|
||||||
|
bind:listElement
|
||||||
|
>
|
||||||
|
{#snippet itemSnippet({ item, isSelected, onclick })}
|
||||||
|
{#if item.type === 'calculator'}
|
||||||
|
<Calculator
|
||||||
|
searchText={item.data.value}
|
||||||
|
mathResult={item.data.result}
|
||||||
|
mathResultType={item.data.resultType}
|
||||||
|
{isSelected}
|
||||||
|
onSelect={onclick}
|
||||||
|
/>
|
||||||
|
{:else if item.type === 'plugin'}
|
||||||
|
{@const assetsPath = path.dirname(item.data.pluginPath) + '/assets'}
|
||||||
|
<ListItemBase
|
||||||
|
title={item.data.title}
|
||||||
|
subtitle={item.data.pluginTitle}
|
||||||
|
icon={item.data.icon || 'app-window-16'}
|
||||||
|
{assetsPath}
|
||||||
|
{isSelected}
|
||||||
|
{onclick}
|
||||||
|
>
|
||||||
|
{#snippet accessories()}
|
||||||
|
<span class="text-muted-foreground ml-auto text-xs whitespace-nowrap"> Command </span>
|
||||||
|
{/snippet}
|
||||||
|
</ListItemBase>
|
||||||
|
{:else if item.type === 'app'}
|
||||||
|
<ListItemBase
|
||||||
|
title={item.data.name}
|
||||||
|
subtitle={item.data.comment}
|
||||||
|
icon={item.data.icon_path ?? 'app-window-16'}
|
||||||
|
{isSelected}
|
||||||
|
{onclick}
|
||||||
|
>
|
||||||
|
{#snippet accessories()}
|
||||||
|
<span class="text-muted-foreground ml-auto text-xs whitespace-nowrap">
|
||||||
|
Application
|
||||||
|
</span>
|
||||||
|
{/snippet}
|
||||||
|
</ListItemBase>
|
||||||
|
{:else if item.type === 'quicklink'}
|
||||||
|
<ListItemBase
|
||||||
|
title={item.data.name}
|
||||||
|
subtitle={item.data.link.replace(/\{argument\}/g, '...')}
|
||||||
|
icon={item.data.icon ?? 'link-16'}
|
||||||
|
{isSelected}
|
||||||
|
{onclick}
|
||||||
|
>
|
||||||
|
{#snippet accessories()}
|
||||||
|
<span class="text-muted-foreground ml-auto text-xs whitespace-nowrap">
|
||||||
|
Quicklink
|
||||||
|
</span>
|
||||||
|
{/snippet}
|
||||||
|
</ListItemBase>
|
||||||
|
{/if}
|
||||||
|
{/snippet}
|
||||||
|
</BaseList>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CommandPaletteActionBar {selectedItem} {actions} />
|
||||||
|
</main>
|
|
@ -5,7 +5,7 @@
|
||||||
import type { PluginInfo } from '@raycast-linux/protocol';
|
import type { PluginInfo } from '@raycast-linux/protocol';
|
||||||
import { listen } from '@tauri-apps/api/event';
|
import { listen } from '@tauri-apps/api/event';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import CommandPalette from '$lib/components/CommandPalette.svelte';
|
import CommandPalette from '$lib/components/command-palette/CommandPalette.svelte';
|
||||||
import PluginRunner from '$lib/components/PluginRunner.svelte';
|
import PluginRunner from '$lib/components/PluginRunner.svelte';
|
||||||
import Extensions from '$lib/components/Extensions.svelte';
|
import Extensions from '$lib/components/Extensions.svelte';
|
||||||
import OAuthView from '$lib/components/OAuthView.svelte';
|
import OAuthView from '$lib/components/OAuthView.svelte';
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue