@@ -251,7 +239,6 @@ import { breakpointsTailwind, useBreakpoints, useNetwork } from "@vueuse/core"
import { computed, reactive, ref, watch } from "vue"
import { useToast } from "~/composables/toast"
import { GetMyTeamsQuery, TeamMemberRole } from "~/helpers/backend/graphql"
-import { getPlatformSpecialKey } from "~/helpers/platformutils"
import { platform } from "~/platform"
import IconDownload from "~icons/lucide/download"
import IconLifeBuoy from "~icons/lucide/life-buoy"
diff --git a/packages/hoppscotch-common/src/components/app/SpotlightSearch.vue b/packages/hoppscotch-common/src/components/app/SpotlightSearch.vue
new file mode 100644
index 000000000..6704390bb
--- /dev/null
+++ b/packages/hoppscotch-common/src/components/app/SpotlightSearch.vue
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+
+
+
diff --git a/packages/hoppscotch-common/src/components/collections/graphql/index.vue b/packages/hoppscotch-common/src/components/collections/graphql/index.vue
index bf95552b4..67fe145fc 100644
--- a/packages/hoppscotch-common/src/components/collections/graphql/index.vue
+++ b/packages/hoppscotch-common/src/components/collections/graphql/index.vue
@@ -192,6 +192,7 @@ import { PersistenceService } from "~/services/persistence"
import { PersistedOAuthConfig } from "~/services/oauth/oauth.service"
import { GQLOptionTabs } from "~/components/graphql/RequestOptions.vue"
import { EditingProperties } from "../Properties.vue"
+import { defineActionHandler } from "~/helpers/actions"
const t = useI18n()
const toast = useToast()
@@ -676,4 +677,11 @@ const resetSelectedData = () => {
editingRequest.value = null
editingRequestIndex.value = null
}
+
+defineActionHandler("collection.new", () => {
+ displayModalAdd(true)
+})
+defineActionHandler("modals.collection.import", () => {
+ displayModalImportExport(true)
+})
diff --git a/packages/hoppscotch-common/src/components/collections/index.vue b/packages/hoppscotch-common/src/components/collections/index.vue
index db0b4f192..9e1fc4d66 100644
--- a/packages/hoppscotch-common/src/components/collections/index.vue
+++ b/packages/hoppscotch-common/src/components/collections/index.vue
@@ -2351,4 +2351,7 @@ const getErrorMessage = (err: GQLError
) => {
defineActionHandler("collection.new", () => {
displayModalAdd(true)
})
+defineActionHandler("modals.collection.import", () => {
+ displayModalImportExport(true)
+})
diff --git a/packages/hoppscotch-common/src/helpers/actions.ts b/packages/hoppscotch-common/src/helpers/actions.ts
index cef62d130..b8fbd1ca8 100644
--- a/packages/hoppscotch-common/src/helpers/actions.ts
+++ b/packages/hoppscotch-common/src/helpers/actions.ts
@@ -36,6 +36,7 @@ export type HoppAction =
| "collection.new" // Create root collection
| "flyouts.chat.open" // Shows the keybinds flyout
| "flyouts.keybinds.toggle" // Shows the keybinds flyout
+ | "modals.collection.import" // Shows the collection import modal
| "modals.search.toggle" // Shows the search modal
| "modals.support.toggle" // Shows the support modal
| "modals.share.toggle" // Shows the share modal
diff --git a/packages/hoppscotch-common/src/layouts/default.vue b/packages/hoppscotch-common/src/layouts/default.vue
index 424d9d947..e7727661d 100644
--- a/packages/hoppscotch-common/src/layouts/default.vue
+++ b/packages/hoppscotch-common/src/layouts/default.vue
@@ -73,7 +73,7 @@ import { useI18n } from "~/composables/i18n"
import { useToast } from "~/composables/toast"
import { InvocationTriggers, defineActionHandler } from "~/helpers/actions"
import { hookKeybindingsListener } from "~/helpers/keybindings"
-import { applySetting } from "~/newstore/settings"
+import { applySetting, toggleSetting } from "~/newstore/settings"
import { platform } from "~/platform"
import { HoppSpotlightSessionEventData } from "~/platform/analytics"
import { PersistenceService } from "~/services/persistence"
@@ -97,6 +97,8 @@ const t = useI18n()
const persistenceService = useService(PersistenceService)
const spotlightService = useService(SpotlightService)
+const HAS_OPENED_SPOTLIGHT = useSetting("HAS_OPENED_SPOTLIGHT")
+
onBeforeMount(() => {
if (!mdAndLarger.value) {
rightSidebar.value = false
@@ -160,6 +162,7 @@ defineActionHandler("modals.search.toggle", (_, trigger) => {
})
showSearch.value = !showSearch.value
+ !HAS_OPENED_SPOTLIGHT.value && toggleSetting("HAS_OPENED_SPOTLIGHT")
})
defineActionHandler("modals.support.toggle", () => {
diff --git a/packages/hoppscotch-common/src/newstore/settings.ts b/packages/hoppscotch-common/src/newstore/settings.ts
index 61ed992f8..fe86430f7 100644
--- a/packages/hoppscotch-common/src/newstore/settings.ts
+++ b/packages/hoppscotch-common/src/newstore/settings.ts
@@ -65,6 +65,8 @@ export type SettingsDef = {
SIDEBAR: boolean
SIDEBAR_ON_LEFT: boolean
COLUMN_LAYOUT: boolean
+
+ HAS_OPENED_SPOTLIGHT: boolean
}
export const getDefaultSettings = (): SettingsDef => ({
@@ -109,6 +111,8 @@ export const getDefaultSettings = (): SettingsDef => ({
SIDEBAR: true,
SIDEBAR_ON_LEFT: false,
COLUMN_LAYOUT: true,
+
+ HAS_OPENED_SPOTLIGHT: false,
})
type ApplySettingPayload = {
diff --git a/packages/hoppscotch-common/src/services/persistence/validation-schemas/index.ts b/packages/hoppscotch-common/src/services/persistence/validation-schemas/index.ts
index 640604286..5efdf7655 100644
--- a/packages/hoppscotch-common/src/services/persistence/validation-schemas/index.ts
+++ b/packages/hoppscotch-common/src/services/persistence/validation-schemas/index.ts
@@ -66,6 +66,8 @@ const SettingsDefSchema = z.object({
cookie: z.boolean().catch(true),
})
),
+
+ HAS_OPENED_SPOTLIGHT: z.optional(z.boolean()),
})
// Common properties shared across REST & GQL collections
diff --git a/packages/hoppscotch-common/src/services/spotlight/searchers/collections.searcher.ts b/packages/hoppscotch-common/src/services/spotlight/searchers/collections.searcher.ts
index 5326661bc..376b7e8c0 100644
--- a/packages/hoppscotch-common/src/services/spotlight/searchers/collections.searcher.ts
+++ b/packages/hoppscotch-common/src/services/spotlight/searchers/collections.searcher.ts
@@ -15,6 +15,7 @@ import {
restCollectionStore,
} from "~/newstore/collections"
import IconFolder from "~icons/lucide/folder"
+import IconImport from "~icons/lucide/folder-down"
import RESTRequestSpotlightEntry from "~/components/app/spotlight/entry/RESTRequest.vue"
import GQLRequestSpotlightEntry from "~/components/app/spotlight/entry/GQLRequest.vue"
import {
@@ -151,6 +152,10 @@ export class CollectionsSpotlightSearcherService
id: `create-collection`,
name: this.t("collection.new"),
})
+ minisearch.add({
+ id: "import-collection",
+ name: this.t("collection.import"),
+ })
}
if (pageCategory === "rest") {
@@ -168,6 +173,11 @@ export class CollectionsSpotlightSearcherService
text: this.t("collection.new"),
}
+ const importCollectionText: SpotlightResultTextType = {
+ type: "text",
+ text: this.t("collection.import_collection"),
+ }
+
scopeHandle.run(() => {
const isPersonalWorkspace = computed(
() => this.workspaceService.currentWorkspace.value.type === "personal"
@@ -183,44 +193,37 @@ export class CollectionsSpotlightSearcherService
results.value = []
return
}
-
- if (pageCategory === "rest") {
- const searchResults = minisearch.search(query).slice(0, 10)
-
- results.value = searchResults.map((result) => ({
- id: result.id,
- text:
- result.id === "create-collection"
- ? newCollectionText
- : {
- type: "custom",
- component: markRaw(RESTRequestSpotlightEntry),
- componentProps: {
- folderPath: result.id.split("rest-")[1],
- },
- },
- icon: markRaw(IconFolder),
- score: result.score,
- }))
- } else if (pageCategory === "graphql") {
- const searchResults = minisearch.search(query).slice(0, 10)
-
- results.value = searchResults.map((result) => ({
- id: result.id,
- text:
- result.id === "create-collection"
- ? newCollectionText
- : {
- type: "custom",
- component: markRaw(GQLRequestSpotlightEntry),
- componentProps: {
- folderPath: result.id.split("gql-")[1],
- },
- },
- icon: markRaw(IconFolder),
- score: result.score,
- }))
+ const getResultText = (id: string): SpotlightResultTextType => {
+ if (id === "create-collection") return newCollectionText
+ else if (id === "import-collection") return importCollectionText
+ return {
+ type: "custom",
+ component: markRaw(
+ pageCategory === "rest"
+ ? RESTRequestSpotlightEntry
+ : GQLRequestSpotlightEntry
+ ),
+ componentProps: {
+ folderPath: id.split(
+ pageCategory === "rest" ? "rest-" : "gql-"
+ )[1],
+ },
+ }
}
+
+ const getResultIcon = (id: string) => {
+ if (id === "import-collection") return markRaw(IconImport)
+ return markRaw(IconFolder)
+ }
+
+ const searchResults = minisearch.search(query).slice(0, 10)
+
+ results.value = searchResults.map((result) => ({
+ id: result.id,
+ text: getResultText(result.id),
+ icon: getResultIcon(result.id),
+ score: result.score,
+ }))
})
})
@@ -288,6 +291,9 @@ export class CollectionsSpotlightSearcherService
public onResultSelect(result: SpotlightSearcherResult): void {
if (result.id === "create-collection") return invokeAction("collection.new")
+ if (result.id === "import-collection")
+ return invokeAction(`modals.collection.import`)
+
const [type, path] = result.id.split("-")
if (type === "rest") {
diff --git a/packages/hoppscotch-common/src/services/spotlight/searchers/teamRequest.searcher.ts b/packages/hoppscotch-common/src/services/spotlight/searchers/teamRequest.searcher.ts
index 2fc82b096..ac3426506 100644
--- a/packages/hoppscotch-common/src/services/spotlight/searchers/teamRequest.searcher.ts
+++ b/packages/hoppscotch-common/src/services/spotlight/searchers/teamRequest.searcher.ts
@@ -1,20 +1,24 @@
import { Service } from "dioc"
import {
+ SpotlightResultTextType,
SpotlightSearcher,
SpotlightSearcherResult,
SpotlightSearcherSessionState,
SpotlightService,
} from ".."
import { getI18n } from "~/modules/i18n"
-import { Ref, computed, effectScope, markRaw, watch } from "vue"
+import { Ref, computed, effectScope, markRaw, ref, watch } from "vue"
import { TeamSearchService } from "~/helpers/teams/TeamsSearch.service"
import { cloneDeep, debounce } from "lodash-es"
import IconFolder from "~icons/lucide/folder"
+import IconImport from "~icons/lucide/folder-down"
import { WorkspaceService } from "~/services/workspace.service"
import RESTTeamRequestEntry from "~/components/app/spotlight/entry/RESTTeamRequestEntry.vue"
import { RESTTabService } from "~/services/tab/rest"
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
import { HoppRESTRequest } from "@hoppscotch/data"
+import MiniSearch from "minisearch"
+import { invokeAction } from "~/helpers/actions"
export class TeamsSpotlightSearcherService
extends Service
@@ -41,9 +45,79 @@ export class TeamsSpotlightSearcherService
this.spotlight.registerSearcher(this)
}
+ 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"
+ }
+ return "other"
+ } catch (e) {
+ return "other"
+ }
+ }
+
createSearchSession(
query: Readonly[>
): [Ref, () => void] {
+ const pageCategory = this.getCurrentPageCategory()
+
+ // Only show the searcher on the REST page
+ if (pageCategory !== "rest") {
+ return [computed(() => ({ loading: false, results: [] })), () => {}]
+ }
+
+ const results = ref([])
+
+ const minisearch = new MiniSearch({
+ fields: ["name"],
+ storeFields: ["name"],
+ searchOptions: {
+ prefix: true,
+ fuzzy: true,
+ boost: {
+ name: 2,
+ },
+ weights: {
+ fuzzy: 0.2,
+ prefix: 0.8,
+ },
+ },
+ })
+
+ minisearch.add({
+ id: `create-collection`,
+ name: this.t("collection.new"),
+ })
+ minisearch.add({
+ id: "import-collection",
+ name: this.t("collection.import"),
+ })
+
+ const newCollectionText: SpotlightResultTextType = {
+ type: "text",
+ text: this.t("collection.new"),
+ }
+
+ const importCollectionText: SpotlightResultTextType = {
+ type: "text",
+ text: this.t("collection.import_collection"),
+ }
+
+ const getResultText = (id: string): SpotlightResultTextType => {
+ if (id === "create-collection") return newCollectionText
+ return importCollectionText
+ }
+
+ const getResultIcon = (id: string) => {
+ if (id === "import-collection") return markRaw(IconImport)
+ return markRaw(IconFolder)
+ }
+
const isTeamWorkspace = computed(
() => this.workspaceService.currentWorkspace.value.type === "team"
)
@@ -59,6 +133,13 @@ export class TeamsSpotlightSearcherService
if (this.workspaceService.currentWorkspace.value.type === "team") {
const teamID = this.workspaceService.currentWorkspace.value.teamID
debouncedSearch(query, teamID)?.catch(() => {})
+ const searchResults = minisearch.search(query).slice(0, 10)
+ results.value = searchResults.map((result) => ({
+ id: result.id,
+ text: getResultText(result.id),
+ icon: getResultIcon(result.id),
+ score: result.score,
+ }))
}
},
{
@@ -91,36 +172,47 @@ export class TeamsSpotlightSearcherService
}
const resultObj = computed(() => {
- return isTeamWorkspace.value
- ? {
- loading: this.teamsSearch.teamsSearchResultsLoading.value,
- results:
- this.teamsSearch.teamsSearchResultsFormattedForSpotlight.value.map(
- (result) => ({
- id: result.request.id,
- icon: markRaw(IconFolder),
- score: 1, // make a better scoring system for this
- text: {
- type: "custom",
- component: markRaw(RESTTeamRequestEntry),
- componentProps: {
- collectionTitles: result.collectionTitles,
- request: result.request,
- },
- },
- })
- ),
- }
- : {
- loading: false,
- results: [],
- }
- })
+ if (isTeamWorkspace.value) {
+ const teamsSearchResults =
+ this.teamsSearch.teamsSearchResultsFormattedForSpotlight.value
+ const minisearchResults = results.value
+ const mergedResults = [
+ ...teamsSearchResults.map((result) => ({
+ id: result.request.id,
+ icon: markRaw(IconFolder),
+ score: 1, // make a better scoring system for this
+ text: {
+ type: "custom",
+ component: markRaw(RESTTeamRequestEntry),
+ componentProps: {
+ collectionTitles: result.collectionTitles,
+ request: result.request,
+ },
+ },
+ })),
+ ...minisearchResults,
+ ] as SpotlightSearcherResult[]
+
+ return {
+ loading: this.teamsSearch.teamsSearchResultsLoading.value,
+ results: mergedResults,
+ }
+ }
+ return {
+ loading: false,
+ results: [],
+ }
+ })
return [resultObj, onSessionEnd]
}
onResultSelect(result: SpotlightSearcherResult): void {
+ if (result.id === "create-collection") return invokeAction("collection.new")
+
+ if (result.id === "import-collection")
+ return invokeAction(`modals.collection.import`)
+
let inheritedProperties: HoppInheritedProperty | undefined = undefined
const selectedRequest = this.teamsSearch.searchResultsRequests[result.id]
]