diff --git a/packages/hoppscotch-common/src/components/app/spotlight/entry/GQLRequest.vue b/packages/hoppscotch-common/src/components/app/spotlight/entry/GQLRequest.vue
new file mode 100644
index 000000000..f9c657b71
--- /dev/null
+++ b/packages/hoppscotch-common/src/components/app/spotlight/entry/GQLRequest.vue
@@ -0,0 +1,65 @@
+
+
+
+
+ {{ folder.name }}
+
+
+
+
+ {{ request.name }}
+
+
+
+
+
diff --git a/packages/hoppscotch-common/src/components/app/spotlight/entry/RESTRequest.vue b/packages/hoppscotch-common/src/components/app/spotlight/entry/RESTRequest.vue
new file mode 100644
index 000000000..f0777b31c
--- /dev/null
+++ b/packages/hoppscotch-common/src/components/app/spotlight/entry/RESTRequest.vue
@@ -0,0 +1,71 @@
+
+
+
+
+ {{ folder.name }}
+
+
+
+
+ {{ request.method.toUpperCase() }}
+
+
+ {{ request.name }}
+
+
+
+
+
diff --git a/packages/hoppscotch-common/src/components/app/spotlight/index.vue b/packages/hoppscotch-common/src/components/app/spotlight/index.vue
index 01b41f224..49feb1630 100644
--- a/packages/hoppscotch-common/src/components/app/spotlight/index.vue
+++ b/packages/hoppscotch-common/src/components/app/spotlight/index.vue
@@ -97,6 +97,7 @@ import { HistorySpotlightSearcherService } from "~/services/spotlight/searchers/
import { UserSpotlightSearcherService } from "~/services/spotlight/searchers/user.searcher"
import { NavigationSpotlightSearcherService } from "~/services/spotlight/searchers/navigation.searcher"
import { SettingsSpotlightSearcherService } from "~/services/spotlight/searchers/settings.searcher"
+import { CollectionsSpotlightSearcherService } from "~/services/spotlight/searchers/collections.searcher"
const t = useI18n()
@@ -114,6 +115,7 @@ useService(HistorySpotlightSearcherService)
useService(UserSpotlightSearcherService)
useService(NavigationSpotlightSearcherService)
useService(SettingsSpotlightSearcherService)
+useService(CollectionsSpotlightSearcherService)
const search = ref("")
diff --git a/packages/hoppscotch-common/src/services/spotlight/searchers/collections.searcher.ts b/packages/hoppscotch-common/src/services/spotlight/searchers/collections.searcher.ts
new file mode 100644
index 000000000..944d34349
--- /dev/null
+++ b/packages/hoppscotch-common/src/services/spotlight/searchers/collections.searcher.ts
@@ -0,0 +1,315 @@
+import { Service } from "dioc"
+import {
+ SpotlightSearcher,
+ SpotlightSearcherResult,
+ SpotlightSearcherSessionState,
+ SpotlightService,
+} from "../"
+import { Ref, computed, effectScope, markRaw, ref, watch } from "vue"
+import { getI18n } from "~/modules/i18n"
+import MiniSearch from "minisearch"
+import {
+ graphqlCollectionStore,
+ restCollectionStore,
+} from "~/newstore/collections"
+import IconFolder from "~icons/lucide/folder"
+import RESTRequestSpotlightEntry from "~/components/app/spotlight/entry/RESTRequest.vue"
+import GQLRequestSpotlightEntry from "~/components/app/spotlight/entry/GQLRequest.vue"
+import { createNewTab } from "~/helpers/rest/tab"
+import { getTabRefWithSaveContext } from "~/helpers/rest/tab"
+import { currentTabID } from "~/helpers/rest/tab"
+import {
+ HoppCollection,
+ HoppGQLRequest,
+ HoppRESTRequest,
+} from "@hoppscotch/data"
+import { setGQLSession } from "~/newstore/GQLSession"
+import { cloneDeep } from "lodash-es"
+import { hoppWorkspaceStore } from "~/newstore/workspace"
+import { changeWorkspace } from "~/newstore/workspace"
+
+/**
+ * A spotlight searcher that searches through the user's collections
+ *
+ * NOTE: Initializing this service registers it as a searcher with the Spotlight Service.
+ */
+export class CollectionsSpotlightSearcherService
+ extends Service
+ implements SpotlightSearcher
+{
+ public static readonly ID = "COLLECTIONS_SPOTLIGHT_SEARCHER_SERVICE"
+
+ private t = getI18n()
+
+ public searcherID = "collections"
+ public searcherSectionTitle = this.t("collection.my_collections")
+
+ private readonly spotlight = this.bind(SpotlightService)
+
+ constructor() {
+ super()
+
+ this.spotlight.registerSearcher(this)
+ }
+
+ private loadGQLDocsIntoMinisearch(minisearch: MiniSearch) {
+ const gqlCollsQueue = [
+ ...graphqlCollectionStore.value.state.map((coll, index) => ({
+ coll: coll,
+ index: `${index}`,
+ })),
+ ]
+
+ while (gqlCollsQueue.length > 0) {
+ const { coll, index } = gqlCollsQueue.shift()!
+
+ gqlCollsQueue.push(
+ ...coll.folders.map((folder, folderIndex) => ({
+ coll: folder,
+ index: `${index}/${folderIndex}`,
+ }))
+ )
+
+ minisearch.addAll(
+ coll.requests.map((req, reqIndex) => ({
+ id: `gql-${index}/${reqIndex}`,
+ name: req.name,
+ }))
+ )
+ }
+ }
+
+ private loadRESTDocsIntoMinisearch(minisearch: MiniSearch) {
+ const restDocsQueue = [
+ ...restCollectionStore.value.state.map((coll, index) => ({
+ coll: coll,
+ index: `${index}`,
+ })),
+ ]
+
+ while (restDocsQueue.length > 0) {
+ const { coll, index } = restDocsQueue.shift()!
+
+ restDocsQueue.push(
+ ...coll.folders.map((folder, folderIndex) => ({
+ coll: folder,
+ index: `${index}/${folderIndex}`,
+ }))
+ )
+
+ minisearch.addAll(
+ coll.requests.map((req, reqIndex) => ({
+ id: `rest-${index}/${reqIndex}`,
+ name: req.name,
+ }))
+ )
+ }
+ }
+
+ private getCurrentPageCategory() {
+ // TODO: Better logic for this ?
+ try {
+ const url = new URL(window.location.href)
+
+ if (url.pathname.startsWith("/graphql")) {
+ return "graphql"
+ } else if (url.pathname === "/") {
+ return "rest"
+ } else {
+ return "other"
+ }
+ } catch (e) {
+ return "other"
+ }
+ }
+
+ public createSearchSession(
+ query: Readonly[>
+ ): [Ref, () => void] {
+ const pageCategory = this.getCurrentPageCategory()
+
+ const minisearch = new MiniSearch({
+ fields: ["name"],
+ storeFields: ["name"],
+ searchOptions: {
+ prefix: true,
+ fuzzy: true,
+ boost: {
+ name: 2,
+ },
+ weights: {
+ fuzzy: 0.2,
+ prefix: 0.8,
+ },
+ },
+ })
+
+ if (pageCategory === "rest") {
+ this.loadRESTDocsIntoMinisearch(minisearch)
+ } else if (pageCategory === "graphql") {
+ this.loadGQLDocsIntoMinisearch(minisearch)
+ }
+
+ const results = ref([])
+
+ const scopeHandle = effectScope()
+
+ scopeHandle.run(() => {
+ watch(query, (query) => {
+ if (pageCategory === "other") {
+ results.value = []
+ return
+ }
+
+ if (pageCategory === "rest") {
+ const searchResults = minisearch.search(query).slice(0, 10)
+
+ results.value = searchResults.map((result) => ({
+ id: result.id,
+ text: {
+ type: "custom",
+ component: markRaw(RESTRequestSpotlightEntry),
+ componentProps: {
+ folderPath: result.id.split("rest-")[1],
+ },
+ },
+ icon: markRaw(IconFolder),
+ score: result.score,
+ }))
+ } else {
+ const searchResults = minisearch.search(query).slice(0, 10)
+
+ results.value = searchResults.map((result) => ({
+ id: result.id,
+ text: {
+ type: "custom",
+ component: markRaw(GQLRequestSpotlightEntry),
+ componentProps: {
+ folderPath: result.id.split("gql-")[1],
+ },
+ },
+ icon: markRaw(IconFolder),
+ score: result.score,
+ }))
+ }
+ })
+ })
+
+ const resultObj = computed(() => ({
+ loading: false,
+ results: results.value,
+ }))
+
+ return [
+ resultObj,
+ () => {
+ scopeHandle.stop()
+ },
+ ]
+ }
+
+ private getRESTFolderFromFolderPath(
+ folderPath: string
+ ): HoppCollection | undefined {
+ try {
+ const folderIndicies = folderPath.split("/").map((x) => parseInt(x))
+
+ let currentFolder =
+ restCollectionStore.value.state[folderIndicies.shift()!]
+
+ while (folderIndicies.length > 0) {
+ const folderIndex = folderIndicies.shift()!
+
+ const folder = currentFolder.folders[folderIndex]
+
+ currentFolder = folder
+ }
+
+ return currentFolder
+ } catch (e) {
+ console.error(e)
+ return undefined
+ }
+ }
+
+ private getGQLFolderFromFolderPath(
+ folderPath: string
+ ): HoppCollection | undefined {
+ try {
+ const folderIndicies = folderPath.split("/").map((x) => parseInt(x))
+
+ let currentFolder =
+ graphqlCollectionStore.value.state[folderIndicies.shift()!]
+
+ while (folderIndicies.length > 0) {
+ const folderIndex = folderIndicies.shift()!
+
+ const folder = currentFolder.folders[folderIndex]
+
+ currentFolder = folder
+ }
+
+ return currentFolder
+ } catch (e) {
+ console.error(e)
+ return undefined
+ }
+ }
+
+ public onResultSelect(result: SpotlightSearcherResult): void {
+ const [type, path] = result.id.split("-")
+
+ if (type === "rest") {
+ const folderPath = path.split("/").map((x) => parseInt(x))
+ const reqIndex = folderPath.pop()!
+
+ if (hoppWorkspaceStore.value.workspace.type !== "personal") {
+ changeWorkspace({
+ type: "personal",
+ })
+ }
+
+ const possibleTab = getTabRefWithSaveContext({
+ originLocation: "user-collection",
+ folderPath: folderPath.join("/"),
+ requestIndex: reqIndex,
+ })
+
+ if (possibleTab) {
+ currentTabID.value = possibleTab.value.id
+ } else {
+ const req = this.getRESTFolderFromFolderPath(folderPath.join("/"))
+ ?.requests[reqIndex]
+
+ if (!req) return
+
+ createNewTab(
+ {
+ request: req,
+ isDirty: false,
+ saveContext: {
+ originLocation: "user-collection",
+ folderPath: folderPath.join("/"),
+ requestIndex: reqIndex,
+ },
+ },
+ true
+ )
+ }
+ } else if (type === "gql") {
+ const folderPath = path.split("/").map((x) => parseInt(x))
+ const reqIndex = folderPath.pop()!
+
+ const req = this.getGQLFolderFromFolderPath(folderPath.join("/"))
+ ?.requests[reqIndex]
+
+ if (!req) return
+
+ setGQLSession({
+ request: cloneDeep(req),
+ schema: "",
+ response: "",
+ })
+ }
+ }
+}
]