diff --git a/packages/hoppscotch-common/locales/en.json b/packages/hoppscotch-common/locales/en.json index 90a91a830..bb85a0270 100644 --- a/packages/hoppscotch-common/locales/en.json +++ b/packages/hoppscotch-common/locales/en.json @@ -606,7 +606,7 @@ "delete_method": "Select DELETE method", "get_method": "Select GET method", "head_method": "Select HEAD method", - "rename": "Rename Current Request", + "rename": "Rename Request", "import_curl": "Import cURL", "show_code": "Generate code snippet", "method": "Method", @@ -649,11 +649,11 @@ }, "spotlight": { "general": { - "help_menu": "Open help and support menu", + "help_menu": "Help and support", "chat": "Chat with support", "open_docs": "Read Documentation", - "open_keybindings": "Open keyboard shortcuts", - "social": "Social links and GitHub", + "open_keybindings": "Keyboard shortcuts", + "social": "Social", "title": "General" }, "miscellaneous": { @@ -661,15 +661,18 @@ "title": "Miscellaneous" }, "request": { - "tab_parameters": "Open parameters tab", - "tab_body": "Open body tab", - "tab_headers": "Open headers tab", - "tab_authorization": "Open authorization tab", - "tab_pre_request_script": "Open pre-request script tab", - "tab_tests": "Open tests tab" + "switch_to": "Switch to", + "select_method": "Select method", + "save_as_new": "Save as new request", + "tab_parameters": "Parameters tab", + "tab_body": "Body tab", + "tab_headers": "Headers tab", + "tab_authorization": "Authorization tab", + "tab_pre_request_script": "Pre-request script tab", + "tab_tests": "Tests tab" }, "response": { - "copy": "Copy response as JSON", + "copy": "Copy response", "download": "Download response as file", "title": "Response" }, @@ -692,6 +695,7 @@ "title": "Teams" }, "tab": { + "duplicate": "Duplicate tab", "close_current": "Close current tab", "close_others": "Close other tabs", "new_tab": "Open a new tab", @@ -703,9 +707,7 @@ "interface": "Interface", "interceptor": "Interceptor" }, - "change_interceptor": "Change Interceptor", "change_language": "Change Language", - "install_extension": "Install Browser Extension", "settings": { "theme": { "black": "Black Mode", @@ -719,8 +721,7 @@ "size_lg": "Change to Large" }, "change_interceptor": "Change Interceptor", - "change_language": "Change Language", - "install_extension": "Install Browser Extension" + "change_language": "Change Language" } }, "sse": { diff --git a/packages/hoppscotch-common/src/components.d.ts b/packages/hoppscotch-common/src/components.d.ts index 484614b1f..b20e316ae 100644 --- a/packages/hoppscotch-common/src/components.d.ts +++ b/packages/hoppscotch-common/src/components.d.ts @@ -29,6 +29,7 @@ declare module 'vue' { AppSpotlightEntry: typeof import('./components/app/spotlight/Entry.vue')['default'] AppSpotlightEntryGQLHistory: typeof import('./components/app/spotlight/entry/GQLHistory.vue')['default'] AppSpotlightEntryGQLRequest: typeof import('./components/app/spotlight/entry/GQLRequest.vue')['default'] + AppSpotlightEntryIconSelected: typeof import('./components/app/spotlight/entry/IconSelected.vue')['default'] AppSpotlightEntryRESTHistory: typeof import('./components/app/spotlight/entry/RESTHistory.vue')['default'] AppSpotlightEntryRESTRequest: typeof import('./components/app/spotlight/entry/RESTRequest.vue')['default'] AppSupport: typeof import('./components/app/Support.vue')['default'] diff --git a/packages/hoppscotch-common/src/components/app/ActionHandler.vue b/packages/hoppscotch-common/src/components/app/ActionHandler.vue index 1ca90fa93..ff4a28271 100644 --- a/packages/hoppscotch-common/src/components/app/ActionHandler.vue +++ b/packages/hoppscotch-common/src/components/app/ActionHandler.vue @@ -1,7 +1,6 @@ diff --git a/packages/hoppscotch-common/src/helpers/actions.ts b/packages/hoppscotch-common/src/helpers/actions.ts index 01899f84a..23286f75d 100644 --- a/packages/hoppscotch-common/src/helpers/actions.ts +++ b/packages/hoppscotch-common/src/helpers/actions.ts @@ -26,14 +26,15 @@ export type HoppAction = | "request.method.delete" // Select DELETE Method | "request.import-curl" // Import cURL | "request.show-code" // Show generated code + | "collection.new" // Create root collection | "flyouts.chat.open" // Shows the keybinds flyout | "flyouts.keybinds.toggle" // Shows the keybinds flyout | "modals.search.toggle" // Shows the search modal | "modals.support.toggle" // Shows the support modal | "modals.share.toggle" // Shows the share modal - | "modals.social.toggle" // Shows the social links modal | "modals.environment.add" // Show add environment modal via context menu | "modals.environment.new" // Add new environment + | "modals.environment.delete-selected" // Delete Selected Environment | "modals.my.environment.edit" // Edit current personal environment | "modals.team.environment.edit" // Edit current team environment | "modals.team.new" // Add new team @@ -108,6 +109,10 @@ type HoppActionArgsMap = { tab: RequestOptionTabs } + "request.duplicate-tab": { + tabID: string + } + "gql.request.open": { request: HoppGQLRequest saveContext?: HoppGQLSaveContext @@ -177,7 +182,7 @@ type InvokeActionFunc = { * @param args The argument passed to the action handler. Optional if action has no args required */ export const invokeAction: InvokeActionFunc = < - A extends HoppAction | HoppActionWithArgs, + A extends HoppAction | HoppActionWithArgs >( action: A, args: ArgOfHoppAction diff --git a/packages/hoppscotch-common/src/newstore/environments.ts b/packages/hoppscotch-common/src/newstore/environments.ts index ed77728a2..691c88665 100644 --- a/packages/hoppscotch-common/src/newstore/environments.ts +++ b/packages/hoppscotch-common/src/newstore/environments.ts @@ -427,6 +427,10 @@ export function getCurrentEnvironment(): Environment { } } +export function getSelectedEnvironmentIndex() { + return environmentsStore.value.selectedEnvironmentIndex +} + export function getSelectedEnvironmentType() { return environmentsStore.value.selectedEnvironmentIndex.type } diff --git a/packages/hoppscotch-common/src/pages/index.vue b/packages/hoppscotch-common/src/pages/index.vue index b44402f81..af144de17 100644 --- a/packages/hoppscotch-common/src/pages/index.vue +++ b/packages/hoppscotch-common/src/pages/index.vue @@ -461,6 +461,9 @@ defineActionHandler("rest.request.open", ({ doc }) => { }) defineActionHandler("rest.request.rename", openReqRenameModal) +defineActionHandler("request.duplicate-tab", ({ tabID }) => { + duplicateTab(tabID) +}) const inspectionService = useService(InspectionService) useService(HeaderInspectorService) 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 c7322a569..833bf3040 100644 --- a/packages/hoppscotch-common/src/services/spotlight/searchers/collections.searcher.ts +++ b/packages/hoppscotch-common/src/services/spotlight/searchers/collections.searcher.ts @@ -1,5 +1,6 @@ import { Service } from "dioc" import { + SpotlightResultTextType, SpotlightSearcher, SpotlightSearcherResult, SpotlightSearcherSessionState, @@ -26,6 +27,7 @@ import { } from "@hoppscotch/data" import { hoppWorkspaceStore } from "~/newstore/workspace" import { changeWorkspace } from "~/newstore/workspace" +import { invokeAction } from "~/helpers/actions" /** * A spotlight searcher that searches through the user's collections @@ -143,6 +145,13 @@ export class CollectionsSpotlightSearcherService }, }) + if (pageCategory === "rest" || pageCategory === "graphql") { + minisearch.add({ + id: `create-collection`, + name: this.t("collection.new"), + }) + } + if (pageCategory === "rest") { this.loadRESTDocsIntoMinisearch(minisearch) } else if (pageCategory === "graphql") { @@ -153,6 +162,11 @@ export class CollectionsSpotlightSearcherService const scopeHandle = effectScope() + const newCollectionText: SpotlightResultTextType = { + type: "text", + text: this.t("collection.new"), + } + scopeHandle.run(() => { watch(query, (query) => { if (pageCategory === "other") { @@ -165,28 +179,34 @@ export class CollectionsSpotlightSearcherService results.value = searchResults.map((result) => ({ id: result.id, - text: { - type: "custom", - component: markRaw(RESTRequestSpotlightEntry), - componentProps: { - folderPath: result.id.split("rest-")[1], - }, - }, + 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 { + } else if (pageCategory === "graphql") { 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], - }, - }, + text: + result.id === "create-collection" + ? newCollectionText + : { + type: "custom", + component: markRaw(GQLRequestSpotlightEntry), + componentProps: { + folderPath: result.id.split("gql-")[1], + }, + }, icon: markRaw(IconFolder), score: result.score, })) @@ -256,6 +276,8 @@ export class CollectionsSpotlightSearcherService } public onResultSelect(result: SpotlightSearcherResult): void { + if (result.id === "create-collection") return invokeAction("collection.new") + const [type, path] = result.id.split("-") if (type === "rest") { diff --git a/packages/hoppscotch-common/src/services/spotlight/searchers/environment.searcher.ts b/packages/hoppscotch-common/src/services/spotlight/searchers/environment.searcher.ts index 2e8f5e2bd..15b421e67 100644 --- a/packages/hoppscotch-common/src/services/spotlight/searchers/environment.searcher.ts +++ b/packages/hoppscotch-common/src/services/spotlight/searchers/environment.searcher.ts @@ -21,30 +21,29 @@ import { StaticSpotlightSearcherService, } from "./base/static.searcher" -import IconEdit from "~icons/lucide/edit" -import IconTrash2 from "~icons/lucide/trash-2" import IconCopy from "~icons/lucide/copy" +import IconEdit from "~icons/lucide/edit" import IconLayers from "~icons/lucide/layers" +import IconTrash2 from "~icons/lucide/trash-2" +import { Service } from "dioc" +import * as TE from "fp-ts/TaskEither" +import { pipe } from "fp-ts/function" +import { cloneDeep } from "lodash-es" +import MiniSearch from "minisearch" +import { map } from "rxjs" import { useStreamStatic } from "~/composables/stream" +import { GQLError } from "~/helpers/backend/GQLClient" +import { deleteTeamEnvironment } from "~/helpers/backend/mutations/TeamEnvironment" import { createEnvironment, currentEnvironment$, - deleteEnvironment, duplicateEnvironment, environmentsStore, getGlobalVariables, selectedEnvironmentIndex$, setSelectedEnvironmentIndex, } from "~/newstore/environments" -import { pipe } from "fp-ts/function" -import * as TE from "fp-ts/TaskEither" -import { deleteTeamEnvironment } from "~/helpers/backend/mutations/TeamEnvironment" -import { GQLError } from "~/helpers/backend/GQLClient" -import { cloneDeep } from "lodash-es" -import { Service } from "dioc" -import MiniSearch from "minisearch" -import { map } from "rxjs" type Doc = { text: string @@ -188,29 +187,6 @@ export class EnvironmentsSpotlightSearcherService extends StaticSpotlightSearche } } - removeSelectedEnvironment = () => { - if (this.selectedEnvIndex.value?.type === "NO_ENV_SELECTED") return - - if (this.selectedEnvIndex.value?.type === "MY_ENV") { - deleteEnvironment(this.selectedEnvIndex.value.index) - // this.toast.success(`${t("state.deleted")}`) - } - - if (this.selectedEnvIndex.value?.type === "TEAM_ENV") { - pipe( - deleteTeamEnvironment(this.selectedEnvIndex.value.teamEnvID), - TE.match( - (err: GQLError) => { - console.error(err) - }, - () => { - // this.toast.success(`${this.t("team_environment.deleted")}`) - } - ) - )() - } - } - public onDocSelected(id: string): void { switch (id) { case "new_environment": @@ -229,7 +205,7 @@ export class EnvironmentsSpotlightSearcherService extends StaticSpotlightSearche }) break case "delete_selected_env": - this.removeSelectedEnvironment() + invokeAction(`modals.environment.delete-selected`) break case "duplicate_selected_env": this.duplicateSelectedEnv() diff --git a/packages/hoppscotch-common/src/services/spotlight/searchers/general.searcher.ts b/packages/hoppscotch-common/src/services/spotlight/searchers/general.searcher.ts index b707d1829..54d5198ba 100644 --- a/packages/hoppscotch-common/src/services/spotlight/searchers/general.searcher.ts +++ b/packages/hoppscotch-common/src/services/spotlight/searchers/general.searcher.ts @@ -7,14 +7,17 @@ import { StaticSpotlightSearcherService, } from "./base/static.searcher" +import IconLinkedIn from "~icons/brands/linkedin" +import IconTwitter from "~icons/brands/twitter" import IconBook from "~icons/lucide/book" -import IconGithub from "~icons/lucide/github" +import IconDiscord from "~icons/lucide/link" +import IconGitHub from "~icons/lucide/github" import IconLifeBuoy from "~icons/lucide/life-buoy" import IconMessageCircle from "~icons/lucide/message-circle" import IconZap from "~icons/lucide/zap" type Doc = { - text: string + text: string | string[] alternates: string[] icon: object | Component } @@ -56,10 +59,25 @@ export class GeneralSpotlightSearcherService extends StaticSpotlightSearcherServ alternates: ["key", "shortcuts", "binding"], icon: markRaw(IconZap), }, - social_links: { - text: this.t("spotlight.general.social"), - alternates: ["social", "github", "binding"], - icon: markRaw(IconGithub), + link_github: { + text: [this.t("spotlight.general.social"), "GitHub"], + alternates: ["social", "github", "link"], + icon: markRaw(IconGitHub), + }, + link_twitter: { + text: [this.t("spotlight.general.social"), "Twitter"], + alternates: ["social", "twitter", "link"], + icon: markRaw(IconTwitter), + }, + link_discord: { + text: [this.t("spotlight.general.social"), "Discord"], + alternates: ["social", "discord", "link"], + icon: markRaw(IconDiscord), + }, + link_linkedin: { + text: [this.t("spotlight.general.social"), "LinkedIn"], + alternates: ["social", "linkedin", "link"], + icon: markRaw(IconLinkedIn), }, }) @@ -87,8 +105,7 @@ export class GeneralSpotlightSearcherService extends StaticSpotlightSearcherServ } } - private openDocs() { - const url = "https://docs.hoppscotch.io" + private openURL(url: string) { window.open(url, "_blank") } @@ -101,13 +118,22 @@ export class GeneralSpotlightSearcherService extends StaticSpotlightSearcherServ invokeAction("flyouts.chat.open") break case "open_docs": - this.openDocs() + this.openURL("https://docs.hoppscotch.io") break case "open_keybindings": invokeAction("flyouts.keybinds.toggle") break - case "social_links": - invokeAction("modals.social.toggle") + case "link_github": + this.openURL("https://hoppscotch.io/github") + break + case "link_twitter": + this.openURL("https://twitter.com/hoppscotch_io") + break + case "link_discord": + this.openURL("https://hoppscotch.io/discord") + break + case "link_linkedin": + this.openURL("https://www.linkedin.com/company/hoppscotch/") break } } diff --git a/packages/hoppscotch-common/src/services/spotlight/searchers/interceptor.searcher.ts b/packages/hoppscotch-common/src/services/spotlight/searchers/interceptor.searcher.ts new file mode 100644 index 000000000..f2f6c9959 --- /dev/null +++ b/packages/hoppscotch-common/src/services/spotlight/searchers/interceptor.searcher.ts @@ -0,0 +1,126 @@ +import { Ref, computed, effectScope, markRaw, ref, unref, watch } from "vue" +import { getI18n } from "~/modules/i18n" +import { + SpotlightSearcher, + SpotlightSearcherResult, + SpotlightSearcherSessionState, + SpotlightService, +} from ".." + +import { Service } from "dioc" +import { useService } from "dioc/vue" +import MiniSearch from "minisearch" +import IconCheckCircle from "~/components/app/spotlight/entry/IconSelected.vue" +import { InterceptorService } from "~/services/interceptor.service" +import IconCircle from "~icons/lucide/circle" + +/** + * This searcher is responsible for searching through the interceptor. + * And switching between them. + */ +export class InterceptorSpotlightSearcherService + extends Service + implements SpotlightSearcher +{ + public static readonly ID = "INTERCEPTOR_SPOTLIGHT_SEARCHER_SERVICE" + + private t = getI18n() + + public searcherID = "interceptor" + public searcherSectionTitle = this.t("settings.interceptor") + + private readonly spotlight = this.bind(SpotlightService) + + constructor() { + super() + + this.spotlight.registerSearcher(this) + } + + private interceptorService = useService(InterceptorService) + + createSearchSession( + query: Readonly> + ): [Ref, () => void] { + const loading = ref(false) + const results = ref([]) + + const minisearch = new MiniSearch({ + fields: ["name", "alternates"], + storeFields: ["name"], + }) + + const interceptorSelection = this.interceptorService + .currentInterceptorID as Ref + + const interceptors = this.interceptorService.availableInterceptors + + minisearch.addAll( + interceptors.value.map((entry) => { + let id = `interceptor-${entry.interceptorID}` + if (entry.interceptorID === interceptorSelection.value) { + id += "-selected" + } + const name = unref(entry.name(this.t)) + return { + id, + name, + alternates: ["interceptor", "change", name], + } + }) + ) + + const scopeHandle = effectScope() + + scopeHandle.run(() => { + watch( + [query], + ([query]) => { + results.value = minisearch + .search(query, { + prefix: true, + fuzzy: true, + boost: { + reltime: 2, + }, + weights: { + fuzzy: 0.2, + prefix: 0.8, + }, + }) + .map((x) => { + return { + id: x.id, + icon: markRaw( + x.id.endsWith("-selected") ? IconCheckCircle : IconCircle + ), + score: x.score, + text: { + type: "text", + text: [this.t("spotlight.section.interceptor"), x.name], + }, + } + }) + }, + { immediate: true } + ) + }) + + const onSessionEnd = () => { + scopeHandle.stop() + minisearch.removeAll() + } + + const resultObj = computed(() => ({ + loading: loading.value, + results: results.value, + })) + + return [resultObj, onSessionEnd] + } + + onResultSelect(result: SpotlightSearcherResult): void { + const selectedInterceptor = result.id.split("-")[1] + this.interceptorService.currentInterceptorID.value = selectedInterceptor + } +} diff --git a/packages/hoppscotch-common/src/services/spotlight/searchers/request.searcher.ts b/packages/hoppscotch-common/src/services/spotlight/searchers/request.searcher.ts index 7866a83f2..9d1589b7c 100644 --- a/packages/hoppscotch-common/src/services/spotlight/searchers/request.searcher.ts +++ b/packages/hoppscotch-common/src/services/spotlight/searchers/request.searcher.ts @@ -1,4 +1,4 @@ -import { Component, markRaw, reactive } from "vue" +import { Component, computed, markRaw, reactive } from "vue" import { invokeAction } from "~/helpers/actions" import { getI18n } from "~/modules/i18n" import { SpotlightSearcherResult, SpotlightService } from ".." @@ -7,12 +7,11 @@ import { StaticSpotlightSearcherService, } from "./base/static.searcher" +import { useRoute } from "vue-router" import { RequestOptionTabs } from "~/components/http/RequestOptions.vue" import { currentActiveTab } from "~/helpers/rest/tab" import IconWindow from "~icons/lucide/app-window" -import IconCheck from "~icons/lucide/check" -import IconChevronLeft from "~icons/lucide/chevron-left" -import IconChevronRight from "~icons/lucide/chevron-right" +import IconCheckCircle from "~icons/lucide/check-circle" import IconCode2 from "~icons/lucide/code-2" import IconCopy from "~icons/lucide/copy" import IconFileCode from "~icons/lucide/file-code" @@ -25,6 +24,7 @@ type Doc = { text: string | string[] alternates: string[] icon: object | Component + excludeFromSearch?: boolean } /** @@ -43,116 +43,160 @@ export class RequestSpotlightSearcherService extends StaticSpotlightSearcherServ private readonly spotlight = this.bind(SpotlightService) + private route = useRoute() + private isRESTPage = computed(() => this.route.name === "index") + private isGQLPage = computed(() => this.route.name === "graphql") + private documents: Record = reactive({ send_request: { text: this.t("shortcut.request.send_request"), alternates: ["request", "send"], icon: markRaw(IconPlay), + excludeFromSearch: computed( + () => !this.isRESTPage.value ?? !this.isGQLPage.value + ), }, save_to_collections: { - text: [ - this.t("request.save_as"), - this.t("shortcut.request.save_to_collections"), - ], + text: this.t("spotlight.request.save_as_new"), alternates: ["save", "collections"], icon: markRaw(IconSave), + excludeFromSearch: computed( + () => !this.isRESTPage.value ?? !this.isGQLPage.value + ), }, save_request: { text: this.t("shortcut.request.save_request"), alternates: ["save", "request"], icon: markRaw(IconSave), + excludeFromSearch: computed( + () => !this.isRESTPage.value ?? !this.isGQLPage.value + ), }, rename_request: { text: this.t("shortcut.request.rename"), alternates: ["rename", "request"], icon: markRaw(IconRename), + excludeFromSearch: computed( + () => !this.isRESTPage.value ?? !this.isGQLPage.value + ), }, copy_request_link: { text: this.t("shortcut.request.copy_request_link"), alternates: ["copy", "link"], icon: markRaw(IconCopy), + excludeFromSearch: computed(() => !this.isRESTPage.value), }, reset_request: { text: this.t("shortcut.request.reset_request"), alternates: ["reset", "request"], icon: markRaw(IconRotateCCW), + excludeFromSearch: computed(() => !this.isRESTPage.value), }, import_curl: { text: this.t("shortcut.request.import_curl"), alternates: ["import", "curl"], icon: markRaw(IconFileCode), + excludeFromSearch: computed(() => !this.isRESTPage.value), }, show_code: { text: this.t("shortcut.request.show_code"), alternates: ["show", "code"], icon: markRaw(IconCode2), + excludeFromSearch: computed(() => !this.isRESTPage.value), }, // Change request method - next_method: { - text: this.t("shortcut.request.next_method"), - alternates: ["next", "method"], - icon: markRaw(IconChevronRight), - }, - previous_method: { - text: this.t("shortcut.request.previous_method"), - alternates: ["previous", "method"], - icon: markRaw(IconChevronLeft), - }, get_method: { - text: this.t("shortcut.request.get_method"), + text: [this.t("spotlight.request.select_method"), "GET"], alternates: ["get", "method"], - icon: markRaw(IconCheck), + icon: markRaw(IconCheckCircle), + excludeFromSearch: computed(() => !this.isRESTPage.value), }, head_method: { - text: this.t("shortcut.request.head_method"), + text: [this.t("spotlight.request.select_method"), "HEAD"], alternates: ["head", "method"], - icon: markRaw(IconCheck), + icon: markRaw(IconCheckCircle), + excludeFromSearch: computed(() => !this.isRESTPage.value), }, post_method: { - text: this.t("shortcut.request.post_method"), + text: [this.t("spotlight.request.select_method"), "POST"], alternates: ["post", "method"], - icon: markRaw(IconCheck), + icon: markRaw(IconCheckCircle), + excludeFromSearch: computed(() => !this.isRESTPage.value), }, put_method: { - text: this.t("shortcut.request.put_method"), + text: [this.t("spotlight.request.select_method"), "PUT"], alternates: ["put", "method"], - icon: markRaw(IconCheck), + icon: markRaw(IconCheckCircle), + excludeFromSearch: computed(() => !this.isRESTPage.value), }, delete_method: { - text: this.t("shortcut.request.delete_method"), + text: [this.t("spotlight.request.select_method"), "DELETE"], alternates: ["delete", "method"], - icon: markRaw(IconCheck), + icon: markRaw(IconCheckCircle), + excludeFromSearch: computed(() => !this.isRESTPage.value), }, // Change sub tabs tab_parameters: { - text: this.t("spotlight.request.tab_parameters"), + text: [ + this.t("spotlight.request.switch_to"), + this.t("spotlight.request.tab_parameters"), + ], alternates: ["parameters", "tab"], icon: markRaw(IconWindow), + excludeFromSearch: computed( + () => !this.isRESTPage.value ?? !this.isGQLPage.value + ), }, tab_body: { - text: this.t("spotlight.request.tab_body"), + text: [ + this.t("spotlight.request.switch_to"), + this.t("spotlight.request.tab_body"), + ], alternates: ["body", "tab"], icon: markRaw(IconWindow), + excludeFromSearch: computed( + () => !this.isRESTPage.value ?? !this.isGQLPage.value + ), }, tab_headers: { - text: this.t("spotlight.request.tab_headers"), + text: [ + this.t("spotlight.request.switch_to"), + this.t("spotlight.request.tab_headers"), + ], alternates: ["headers", "tab"], icon: markRaw(IconWindow), + excludeFromSearch: computed( + () => !this.isRESTPage.value ?? !this.isGQLPage.value + ), }, tab_authorization: { - text: this.t("spotlight.request.tab_authorization"), + text: [ + this.t("spotlight.request.switch_to"), + this.t("spotlight.request.tab_authorization"), + ], alternates: ["authorization", "tab"], icon: markRaw(IconWindow), + excludeFromSearch: computed( + () => !this.isRESTPage.value ?? !this.isGQLPage.value + ), }, tab_pre_request_script: { - text: this.t("spotlight.request.tab_pre_request_script"), + text: [ + this.t("spotlight.request.switch_to"), + this.t("spotlight.request.tab_pre_request_script"), + ], alternates: ["pre-request", "script", "tab"], icon: markRaw(IconWindow), + excludeFromSearch: computed(() => !this.isRESTPage.value), }, tab_tests: { - text: this.t("spotlight.request.tab_tests"), + text: [ + this.t("spotlight.request.switch_to"), + this.t("spotlight.request.tab_tests"), + ], alternates: ["tests", "tab"], icon: markRaw(IconWindow), + excludeFromSearch: computed(() => !this.isRESTPage.value), }, }) @@ -209,12 +253,6 @@ export class RequestSpotlightSearcherService extends StaticSpotlightSearcherServ case "reset_request": invokeAction("request.reset") break - case "next_method": - invokeAction("request.method.next") - break - case "previous_method": - invokeAction("request.method.prev") - break case "get_method": invokeAction("request.method.get") break diff --git a/packages/hoppscotch-common/src/services/spotlight/searchers/settings.searcher.ts b/packages/hoppscotch-common/src/services/spotlight/searchers/settings.searcher.ts index ccf4a92f8..8b53dce39 100644 --- a/packages/hoppscotch-common/src/services/spotlight/searchers/settings.searcher.ts +++ b/packages/hoppscotch-common/src/services/spotlight/searchers/settings.searcher.ts @@ -10,11 +10,10 @@ import { } from "./base/static.searcher" import IconCloud from "~icons/lucide/cloud" +import IconGlobe from "~icons/lucide/globe" import IconMonitor from "~icons/lucide/monitor" import IconMoon from "~icons/lucide/moon" import IconSun from "~icons/lucide/sun" -import IconGlobe from "~icons/lucide/globe" -import IconShieldCheck from "~icons/lucide/shield-check" import IconType from "~icons/lucide/type" type Doc = { @@ -128,22 +127,6 @@ export class SettingsSpotlightSearcherService extends StaticSpotlightSearcherSer alternates: ["language", "change language"], icon: markRaw(IconGlobe), }, - change_interceptor: { - text: [ - this.t("spotlight.section.interceptor"), - this.t("spotlight.settings.change_interceptor"), - ], - alternates: ["interceptor", "change interceptor"], - icon: markRaw(IconShieldCheck), - }, - install_ext: { - text: [ - this.t("spotlight.section.interceptor"), - this.t("spotlight.settings.install_extension"), - ], - alternates: ["install extension", "extension", "interceptor"], - icon: markRaw(IconShieldCheck), - }, }) constructor() { @@ -174,27 +157,12 @@ export class SettingsSpotlightSearcherService extends StaticSpotlightSearcherSer applySetting("BG_COLOR", theme) } - installExtension() { - const url = navigator.userAgent.includes("Firefox") - ? "https://addons.mozilla.org/en-US/firefox/addon/hoppscotch" - : "https://chrome.google.com/webstore/detail/hoppscotch-browser-extens/amknoiejhlmhancpahfcfcfhllgkpbld" - window.open(url, "_blank") - } - public onDocSelected(id: string): void { switch (id) { - case "change_interceptor": - invokeAction("navigation.jump.settings") - break - case "change_lang": invokeAction("navigation.jump.settings") break - case "install_ext": - this.installExtension() - break - // theme actions case "theme_system": invokeAction("settings.theme.system") diff --git a/packages/hoppscotch-common/src/services/spotlight/searchers/tab.searcher.ts b/packages/hoppscotch-common/src/services/spotlight/searchers/tab.searcher.ts index 7bc180431..6240c8abc 100644 --- a/packages/hoppscotch-common/src/services/spotlight/searchers/tab.searcher.ts +++ b/packages/hoppscotch-common/src/services/spotlight/searchers/tab.searcher.ts @@ -1,4 +1,4 @@ -import { Component, markRaw, reactive } from "vue" +import { Component, computed, markRaw, reactive } from "vue" import { getI18n } from "~/modules/i18n" import { SpotlightSearcherResult, SpotlightService } from ".." import { @@ -6,19 +6,23 @@ import { StaticSpotlightSearcherService, } from "./base/static.searcher" +import { useRoute } from "vue-router" +import { getDefaultRESTRequest } from "~/helpers/rest/default" import { closeOtherTabs, closeTab, createNewTab, currentTabID, + getActiveTabs, } from "~/helpers/rest/tab" import IconWindow from "~icons/lucide/app-window" -import { getDefaultRESTRequest } from "~/helpers/rest/default" +import { invokeAction } from "~/helpers/actions" type Doc = { text: string alternates: string[] icon: object | Component + excludeFromSearch?: boolean } /** @@ -37,21 +41,39 @@ export class TabSpotlightSearcherService extends StaticSpotlightSearcherService< private readonly spotlight = this.bind(SpotlightService) + private route = useRoute() + private showAction = computed( + () => this.route.name === "index" ?? this.route.name === "graphql" + ) + private documents: Record = reactive({ + duplicate_tab: { + text: this.t("spotlight.tab.duplicate"), + alternates: ["tab", "duplicate", "duplicate tab"], + icon: markRaw(IconWindow), + excludeFromSearch: computed(() => !this.showAction.value), + }, close_current_tab: { text: this.t("spotlight.tab.close_current"), alternates: ["tab", "close", "close tab"], icon: markRaw(IconWindow), + excludeFromSearch: computed( + () => !this.showAction.value ?? getActiveTabs().value.length === 1 + ), }, close_other_tabs: { text: this.t("spotlight.tab.close_others"), alternates: ["tab", "close", "close all"], icon: markRaw(IconWindow), + excludeFromSearch: computed( + () => !this.showAction.value ?? getActiveTabs().value.length < 2 + ), }, open_new_tab: { text: this.t("spotlight.tab.new_tab"), alternates: ["tab", "new", "open tab"], icon: markRaw(IconWindow), + excludeFromSearch: computed(() => !this.showAction.value), }, }) @@ -80,6 +102,10 @@ export class TabSpotlightSearcherService extends StaticSpotlightSearcherService< } public onDocSelected(id: string): void { + if (id === "duplicate_tab") + invokeAction("request.duplicate-tab", { + tabID: currentTabID.value, + }) if (id === "close_current_tab") closeTab(currentTabID.value) if (id === "close_other_tabs") closeOtherTabs(currentTabID.value) if (id === "open_new_tab") diff --git a/packages/hoppscotch-common/src/services/spotlight/searchers/workspace.searcher.ts b/packages/hoppscotch-common/src/services/spotlight/searchers/workspace.searcher.ts index 8dd1d0d8d..d1256d5f7 100644 --- a/packages/hoppscotch-common/src/services/spotlight/searchers/workspace.searcher.ts +++ b/packages/hoppscotch-common/src/services/spotlight/searchers/workspace.searcher.ts @@ -8,7 +8,7 @@ import { ref, watch, } from "vue" -import { activeActions$, invokeAction } from "~/helpers/actions" +import { invokeAction } from "~/helpers/actions" import { getI18n } from "~/modules/i18n" import { SpotlightSearcher, @@ -24,6 +24,7 @@ import { import { Service } from "dioc" import * as E from "fp-ts/Either" import MiniSearch from "minisearch" +import IconCheckCircle from "~/components/app/spotlight/entry/IconSelected.vue" import { useStreamStatic } from "~/composables/stream" import { runGQLQuery } from "~/helpers/backend/GQLClient" import { GetMyTeamsDocument, GetMyTeamsQuery } from "~/helpers/backend/graphql" @@ -36,7 +37,7 @@ import IconUserPlus from "~icons/lucide/user-plus" import IconUsers from "~icons/lucide/users" type Doc = { - text: string + text: string | string[] alternates: string[] icon: object | Component excludeFromSearch?: boolean @@ -66,14 +67,6 @@ export class WorkspaceSpotlightSearcherService extends StaticSpotlightSearcherSe } )[0] - private activeActions = useStreamStatic(activeActions$, [], () => { - /* noop */ - })[0] - - private isLoggedInUser = computed(() => - this.activeActions.value.includes("user.logout") - ) - private isTeamSelected = computed( () => this.workspace.value.type === "team" && @@ -82,31 +75,33 @@ export class WorkspaceSpotlightSearcherService extends StaticSpotlightSearcherSe private documents: Record = reactive({ new_team: { - text: this.t("spotlight.workspace.new"), + text: [this.t("team.title"), this.t("spotlight.workspace.new")], alternates: ["new", "team", "workspace"], icon: markRaw(IconUsers), - excludeFromSearch: computed(() => !this.isLoggedInUser.value), }, edit_team: { - text: this.t("spotlight.workspace.edit"), + text: [this.t("team.title"), this.t("spotlight.workspace.edit")], alternates: ["edit", "team", "workspace"], icon: markRaw(IconEdit), excludeFromSearch: computed(() => !this.isTeamSelected.value), }, invite_members: { - text: this.t("spotlight.workspace.invite"), + text: [this.t("team.title"), this.t("spotlight.workspace.invite")], alternates: ["invite", "members", "workspace"], icon: markRaw(IconUserPlus), excludeFromSearch: computed(() => !this.isTeamSelected.value), }, delete_team: { - text: this.t("spotlight.workspace.delete"), + text: [this.t("team.title"), this.t("spotlight.workspace.delete")], alternates: ["delete", "team", "workspace"], icon: markRaw(IconTrash2), excludeFromSearch: computed(() => !this.isTeamSelected.value), }, switch_to_personal: { - text: this.t("spotlight.workspace.switch_to_personal"), + text: [ + this.t("team.title"), + this.t("spotlight.workspace.switch_to_personal"), + ], alternates: ["switch", "team", "workspace", "personal"], icon: markRaw(IconUser), excludeFromSearch: computed(() => !this.isTeamSelected.value), @@ -145,8 +140,13 @@ export class WorkspaceSpotlightSearcherService extends StaticSpotlightSearcherSe } public onDocSelected(id: string): void { - if (id === "new_team") invokeAction(`modals.team.new`) - else if (id === "edit_team") invokeAction(`modals.team.edit`) + if (id === "new_team") { + if (platform.auth.getCurrentUser()) { + invokeAction(`modals.team.new`) + } else { + invokeAction(`modals.login.toggle`) + } + } else if (id === "edit_team") invokeAction(`modals.team.edit`) else if (id === "invite_members") invokeAction(`modals.team.invite`) else if (id === "delete_team") this.deleteTeam() else if (id === "switch_to_personal") @@ -197,6 +197,14 @@ export class SwitchWorkspaceSpotlightSearcherService }) } + private workspace = useStreamStatic( + workspaceStatus$, + { type: "personal" }, + () => { + /* noop */ + } + )[0] + createSearchSession( query: Readonly> ): [Ref, () => void] { @@ -211,8 +219,16 @@ export class SwitchWorkspaceSpotlightSearcherService this.fetchMyTeams().then((teams) => { minisearch.addAll( teams.map((entry) => { + let id = `workspace-${entry.id}` + // if id matches add -selected to it + if ( + this.workspace.value.type === "team" && + this.workspace.value.teamID === entry.id + ) { + id += "-selected" + } return { - id: `workspace-${entry.id}`, + id, name: entry.name, alternates: ["team", "workspace", "change", "switch"], } @@ -241,7 +257,9 @@ export class SwitchWorkspaceSpotlightSearcherService .map((x) => { return { id: x.id, - icon: markRaw(IconUsers), + icon: markRaw( + x.id.endsWith("-selected") ? IconCheckCircle : IconUsers + ), score: x.score, text: { type: "text",