From d2d14f5cce7ca10b8c02d07ffc2151ecd95211c3 Mon Sep 17 00:00:00 2001 From: Catalin Costache Date: Sun, 8 Dec 2024 17:42:04 +0200 Subject: [PATCH] feat: multiple vaults support in raycast --- raycast/src/bookmarks.tsx | 105 ++++++++++++++++++++++---------------- raycast/src/fetch.ts | 40 +++++++++++++-- 2 files changed, 98 insertions(+), 47 deletions(-) diff --git a/raycast/src/bookmarks.tsx b/raycast/src/bookmarks.tsx index 05bdfd9..44ecd59 100644 --- a/raycast/src/bookmarks.tsx +++ b/raycast/src/bookmarks.tsx @@ -6,7 +6,7 @@ import { List, } from "@raycast/api"; import { ReactNode, useEffect, useState } from "react"; -import { searchBookmarks } from "./fetch"; +import { searchBookmarks, VaultResults } from "./fetch"; import debounce from "lodash.debounce"; import { IndexedResult, ResultNode } from "obsidian-tree-search/src/search"; import React from "react"; @@ -18,15 +18,25 @@ export interface Preferences { type TreeNodeSearchProps = { node: ResultNode; + vault: string; + vaultColor: string; level: number; minExpand: number; }; +function getVaultColor(vault: string, vaults: string[]): string { + if (vaults.length == 1) return ""; + + const idx = vaults.indexOf(vault); + if (idx == -1) return "🌕"; + return ["🔵", "🟢", "🟠", "🟣", "🔴", "🟡"][idx % 6]; +} + export const IndividualListItem = (props: TreeNodeSearchProps) => { const item = props.node; const actionsAccumulator: ReactNode[] = []; - const tokenText = RaycastTokenRenderer(item.attrs.tokens, actionsAccumulator); - actionsAccumulator.push(); // default open action + const tokenText = RaycastTokenRenderer(item.attrs.tokens, actionsAccumulator, props.vault); + actionsAccumulator.push(); // default open action function getIcon(item: ResultNode) { if ( @@ -54,17 +64,17 @@ export const IndividualListItem = (props: TreeNodeSearchProps) => { 0 ? "|" : ""}${"–".repeat(props.level)} ${tokenText}`} - // subtitle={{ value: item.parents.join(",")}} - // keywords={item.attrs.searchKey.split(" ")} - accessories={[{ icon: getIcon(item), tooltip: item.attrs.nodeType }]} - // icon={getIcon(item)} + accessories={[ + { icon: getIcon(item), tooltip: item.attrs.nodeType }, + { text: props.vaultColor, tooltip: props.vault }, + ]} detail={ @@ -77,11 +87,14 @@ export const IndividualListItem = (props: TreeNodeSearchProps) => { export const RaycastTreeList = (props: TreeNodeSearchProps) => { return ( <> - - {props.node.children.map((child) => ( + + {props.node.children.map((child, idx) => ( ))} @@ -92,10 +105,7 @@ export const RaycastTreeList = (props: TreeNodeSearchProps) => { export default function Command() { const [searchText, setSearchText] = useState(""); - const [filtered, setFiltered] = useState({ - nodes: [], - total: 0, - }); + const [filtered, setFiltered] = useState([]); const debouncer = debounce(async (searchText: string) => { if (!searchText) { @@ -106,8 +116,8 @@ export default function Command() { searchText, getPreferenceValues(), ); - setFiltered(result.data); - }, 300); + setFiltered(result); + }, 100); useEffect(() => { debouncer(searchText); @@ -126,40 +136,49 @@ export default function Command() { onSearchTextChange={setSearchText} isShowingDetail={false} > - {filtered.nodes.map((item) => ( - - ))} + {filtered.flatMap((vault) => + vault.results.nodes.map((item, idx) => ( + it.vault))} vault={vault.vault} node={item} level={0} minExpand={5} /> + )) + )} ); } -function AdvancedUriAction(props: { item: ResultNode }) { +function AdvancedUriAction(props: { item: ResultNode, vault: string }) { const item = props.item; - return ( + return <> - ); + + ; } -function getMarkdownUri(location: ResultNode["attrs"]["location"]) { - return `[${location.path}](${getUrl(location)})`; +function getMarkdownUri(location: ResultNode["attrs"]["location"], vault: string) { + return `[${location.path}](${getUrl(location, vault)})`; } -function getUrl(item: ResultNode["attrs"]["location"]): string { - const uri = `filepath=${item.path}&line=${item.position.start.line + 1}&column=${item.position.start.ch + 1}`; +function getUrl(item: ResultNode["attrs"]["location"], vault: string): string { + const uri = `vault=${vault}&filepath=${item.path}&line=${item.position.start.line + 1}&column=${item.position.start.ch + 1}`; return `obsidian://adv-uri?${encodeURI(uri)}`; } -function RaycastTokenRenderer(tokens: Token[], actions: ReactNode[]): string { +function RaycastTokenRenderer(tokens: Token[], actions: ReactNode[], vault: string): string { if (tokens.length == 0) return ""; const token = tokens[0]; if (token.type == "inline" && token.children) { - return RaycastTokenRenderer(token.children, actions); + return RaycastTokenRenderer(token.children, actions, vault); } if (token.type == "obsidian_link") { @@ -169,12 +188,12 @@ function RaycastTokenRenderer(tokens: Token[], actions: ReactNode[]): string { actions.push( , ); return ( - "🔹" + token.content + RaycastTokenRenderer(tokens.slice(1), actions) + "🔹" + token.content + RaycastTokenRenderer(tokens.slice(1), actions, vault) ); } @@ -186,11 +205,11 @@ function RaycastTokenRenderer(tokens: Token[], actions: ReactNode[]): string { , ); - return "🔗 " + content + RaycastTokenRenderer(tokens.slice(2), actions); + return "🔗 " + content + RaycastTokenRenderer(tokens.slice(2), actions, vault); } if (token.type == "link_close") { - return RaycastTokenRenderer(tokens.slice(1), actions); + return RaycastTokenRenderer(tokens.slice(1), actions, vault); } if (token.type == "text") { @@ -203,35 +222,35 @@ function RaycastTokenRenderer(tokens: Token[], actions: ReactNode[]): string { ); return ( - "🔗 " + token.content + RaycastTokenRenderer(tokens.slice(1), actions) + "🔗 " + token.content + RaycastTokenRenderer(tokens.slice(1), actions, vault) ); } - return token.content + RaycastTokenRenderer(tokens.slice(1), actions); + return token.content + RaycastTokenRenderer(tokens.slice(1), actions, vault); } if (token.type == "strong_open") { - return token.content + RaycastTokenRenderer(tokens.slice(1), actions); + return token.content + RaycastTokenRenderer(tokens.slice(1), actions, vault); } if (token.type == "strong_close") { - return token.content + RaycastTokenRenderer(tokens.slice(1), actions); + return token.content + RaycastTokenRenderer(tokens.slice(1), actions, vault); } if (token.type == "em_open") { - return token.content + RaycastTokenRenderer(tokens.slice(1), actions); + return token.content + RaycastTokenRenderer(tokens.slice(1), actions, vault); } if (token.type == "softbreak") { - return token.content + RaycastTokenRenderer(tokens.slice(1), actions); + return token.content + RaycastTokenRenderer(tokens.slice(1), actions, vault); } if (token.type == "s_open") { - return token.content + RaycastTokenRenderer(tokens.slice(1), actions); + return token.content + RaycastTokenRenderer(tokens.slice(1), actions, vault); } if (token.type == "image") { return ( - "🖼️ " + token.content + RaycastTokenRenderer(tokens.slice(1), actions) + "🖼️ " + token.content + RaycastTokenRenderer(tokens.slice(1), actions, vault) ); } @@ -243,11 +262,11 @@ function RaycastTokenRenderer(tokens: Token[], actions: ReactNode[]): string { />, ); return ( - "📋 " + token.content + RaycastTokenRenderer(tokens.slice(1), actions) + "📋 " + token.content + RaycastTokenRenderer(tokens.slice(1), actions, vault) ); } // if (!token.type.includes("_close")) console.log("tokens not rendered: ", tokens) - return token.content + RaycastTokenRenderer(tokens.slice(1), actions); + return token.content + RaycastTokenRenderer(tokens.slice(1), actions, vault); } diff --git a/raycast/src/fetch.ts b/raycast/src/fetch.ts index ca0cb61..30c2e81 100644 --- a/raycast/src/fetch.ts +++ b/raycast/src/fetch.ts @@ -1,9 +1,15 @@ import axios from "axios"; +import { it } from "node:test"; import { IndexedResult } from "obsidian-tree-search/src/search"; -export async function fetchData(query: string, preferences: Preferences) { +export interface VaultResults { + vault: string; + results: IndexedResult; +} + +export async function fetchData(query: string, socketPath:string): Promise<{ data: IndexedResult }> { const http = axios.create({ - socketPath: preferences.socketPath, + socketPath: socketPath, baseURL: `http://localhost`, timeout: 20000, }); @@ -17,9 +23,35 @@ export async function fetchData(query: string, preferences: Preferences) { }); } +function extractVaultName(path: string): string { + // /tmp/raycast-Obsidian Vault.sock + return path.replace("/tmp/raycast-", "").replace(".sock", ""); +} + export async function searchBookmarks( query: string, preferences: Preferences, -): Promise<{ data: IndexedResult }> { - return fetchData(query, preferences); +): Promise { + + const vaults = preferences.socketPath.split(",").map( + it => { + return { + socket: it.trim(), + vault: extractVaultName(it) + } + } + ); + + const promises = vaults.map(it => getVaultResults(query, it)); + const results = await Promise.all(promises); + return results; +} + +async function getVaultResults(query: string, it: { socket: string; vault: string}): Promise { + const data = await fetchData(query, it.socket); + return { + vault: it.vault, + results: data.data + } } +