feat: expanded search capabilities of spotlight (#3255)
Co-authored-by: Andrew Bastin <andrewbastin.k@gmail.com>
This commit is contained in:
@@ -69,6 +69,8 @@
|
||||
"invite": "Invite",
|
||||
"invite_description": "Hoppscotch is an open source API development ecosystem. We designed a simple and intuitive interface for creating and managing your APIs. Hoppscotch is a tool that helps you build, test, document and share your APIs.",
|
||||
"invite_your_friends": "Invite your friends",
|
||||
"social_links": "Social links",
|
||||
"social_description": "Follow us on social media to stay updated with the latest news, updates and releases.",
|
||||
"join_discord_community": "Join our Discord community",
|
||||
"keyboard_shortcuts": "Keyboard shortcuts",
|
||||
"name": "Hoppscotch",
|
||||
@@ -203,6 +205,7 @@
|
||||
"create_new": "Create new environment",
|
||||
"created": "Environment created",
|
||||
"deleted": "Environment deletion",
|
||||
"duplicated": "Environment duplicated",
|
||||
"edit": "Edit Environment",
|
||||
"global": "Global",
|
||||
"empty_variables": "No variables",
|
||||
@@ -220,6 +223,7 @@
|
||||
"replace_with_variable": "Replace with variable",
|
||||
"scope": "Scope",
|
||||
"select": "Select environment",
|
||||
"set": "Set environment",
|
||||
"set_as_environment": "Set as environment",
|
||||
"team_environments": "Team Environments",
|
||||
"title": "Environments",
|
||||
@@ -594,6 +598,9 @@
|
||||
"delete_method": "Select DELETE method",
|
||||
"get_method": "Select GET method",
|
||||
"head_method": "Select HEAD method",
|
||||
"rename": "Rename Current Request",
|
||||
"import_curl": "Import cURL",
|
||||
"show_code": "Show generated code",
|
||||
"method": "Method",
|
||||
"next_method": "Select Next method",
|
||||
"post_method": "Select POST method",
|
||||
@@ -602,6 +609,7 @@
|
||||
"reset_request": "Reset Request",
|
||||
"save_to_collections": "Save to Collections",
|
||||
"send_request": "Send Request",
|
||||
"save_request": "Save Request",
|
||||
"title": "Request"
|
||||
},
|
||||
"response": {
|
||||
@@ -632,6 +640,55 @@
|
||||
"url": "URL"
|
||||
},
|
||||
"spotlight": {
|
||||
"general": {
|
||||
"help_menu": "Open help and support menu",
|
||||
"chat": "Chat with support",
|
||||
"open_docs": "Open documentation",
|
||||
"open_keybindings": "Open keyboard shortcuts",
|
||||
"social": "Social links and GitHub",
|
||||
"title": "General"
|
||||
},
|
||||
"miscellaneous": {
|
||||
"invite": "Invite people to Hoppscotch",
|
||||
"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"
|
||||
},
|
||||
"response": {
|
||||
"copy": "Copy response as JSON",
|
||||
"download": "Download response as file",
|
||||
"title": "Response"
|
||||
},
|
||||
"environments": {
|
||||
"new": "Create new environment",
|
||||
"new_variable": "Create a new environment variable",
|
||||
"edit": "Edit selected environment",
|
||||
"delete": "Delete selected environment",
|
||||
"duplicate": "Duplicate selected environment",
|
||||
"edit_global": "Edit global environment",
|
||||
"duplicate_global": "Duplicate global environment",
|
||||
"title": "Environments"
|
||||
},
|
||||
"workspace": {
|
||||
"new": "Create new team",
|
||||
"edit": "Edit selected team",
|
||||
"delete": "Delete selected team",
|
||||
"invite": "Invite people to team",
|
||||
"switch_to_personal": "Switch to personal workspace",
|
||||
"title": "Teams"
|
||||
},
|
||||
"tab": {
|
||||
"close_current": "Close current tab",
|
||||
"close_others": "Close others tab",
|
||||
"new_tab": "Open a new tab",
|
||||
"title": "Tabs"
|
||||
},
|
||||
"section": {
|
||||
"user": "User",
|
||||
"theme": "Theme",
|
||||
@@ -649,9 +706,9 @@
|
||||
"system": "System Mode"
|
||||
},
|
||||
"font": {
|
||||
"size_sm": "Change Font Size to Small",
|
||||
"size_md": "Change Font Size to Medium",
|
||||
"size_lg": "Change Font Size to Large"
|
||||
"size_sm": "Change to Small",
|
||||
"size_md": "Change to Medium",
|
||||
"size_lg": "Change to Large"
|
||||
},
|
||||
"change_interceptor": "Change Interceptor",
|
||||
"change_language": "Change Language",
|
||||
|
||||
15
packages/hoppscotch-common/src/components.d.ts
vendored
15
packages/hoppscotch-common/src/components.d.ts
vendored
@@ -1,11 +1,11 @@
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// Generated by unplugin-vue-components
|
||||
// generated by unplugin-vue-components
|
||||
// We suggest you to commit this file into source control
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
import '@vue/runtime-core'
|
||||
|
||||
export {}
|
||||
|
||||
declare module 'vue' {
|
||||
declare module '@vue/runtime-core' {
|
||||
export interface GlobalComponents {
|
||||
AppActionHandler: typeof import('./components/app/ActionHandler.vue')['default']
|
||||
AppAnnouncement: typeof import('./components/app/Announcement.vue')['default']
|
||||
@@ -24,10 +24,13 @@ declare module 'vue' {
|
||||
AppShortcutsEntry: typeof import('./components/app/ShortcutsEntry.vue')['default']
|
||||
AppShortcutsPrompt: typeof import('./components/app/ShortcutsPrompt.vue')['default']
|
||||
AppSidenav: typeof import('./components/app/Sidenav.vue')['default']
|
||||
AppSocial: typeof import('./components/app/Social.vue')['default']
|
||||
AppSpotlight: typeof import('./components/app/spotlight/index.vue')['default']
|
||||
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']
|
||||
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']
|
||||
ButtonPrimary: typeof import('./../../hoppscotch-ui/src/components/button/Primary.vue')['default']
|
||||
ButtonSecondary: typeof import('./../../hoppscotch-ui/src/components/button/Secondary.vue')['default']
|
||||
@@ -81,6 +84,7 @@ declare module 'vue' {
|
||||
HoppButtonPrimary: typeof import('@hoppscotch/ui')['HoppButtonPrimary']
|
||||
HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary']
|
||||
HoppSmartAnchor: typeof import('@hoppscotch/ui')['HoppSmartAnchor']
|
||||
HoppSmartAutoComplete: typeof import('@hoppscotch/ui')['HoppSmartAutoComplete']
|
||||
HoppSmartCheckbox: typeof import('@hoppscotch/ui')['HoppSmartCheckbox']
|
||||
HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal']
|
||||
HoppSmartExpand: typeof import('@hoppscotch/ui')['HoppSmartExpand']
|
||||
@@ -210,4 +214,5 @@ declare module 'vue' {
|
||||
WorkspaceCurrent: typeof import('./components/workspace/Current.vue')['default']
|
||||
WorkspaceSelector: typeof import('./components/workspace/Selector.vue')['default']
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,17 +1,57 @@
|
||||
<template>
|
||||
<AppShortcuts :show="showShortcuts" @close="showShortcuts = false" />
|
||||
<AppShare :show="showShare" @hide-modal="showShare = false" />
|
||||
<AppSocial :show="showSocial" @hide-modal="showSocial = false" />
|
||||
<FirebaseLogin :show="showLogin" @hide-modal="showLogin = false" />
|
||||
|
||||
<HoppSmartConfirmModal
|
||||
:show="confirmRemove"
|
||||
:title="t('confirm.remove_team')"
|
||||
@hide-modal="confirmRemove = false"
|
||||
@resolve="deleteTeam()"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue"
|
||||
import { defineActionHandler } from "~/helpers/actions"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { deleteTeam as backendDeleteTeam } from "~/helpers/backend/mutations/Team"
|
||||
import { defineActionHandler, invokeAction } from "~/helpers/actions"
|
||||
import { showChat } from "~/modules/crisp"
|
||||
import { useToast } from "~/composables/toast"
|
||||
import { useI18n } from "~/composables/i18n"
|
||||
|
||||
const toast = useToast()
|
||||
const t = useI18n()
|
||||
|
||||
const showShortcuts = ref(false)
|
||||
const showShare = ref(false)
|
||||
const showSocial = ref(false)
|
||||
const showLogin = ref(false)
|
||||
|
||||
const confirmRemove = ref(false)
|
||||
|
||||
const teamID = ref<string | null>(null)
|
||||
|
||||
const deleteTeam = () => {
|
||||
if (!teamID.value) return
|
||||
pipe(
|
||||
backendDeleteTeam(teamID.value),
|
||||
TE.match(
|
||||
(err) => {
|
||||
// TODO: Better errors ? We know the possible errors now
|
||||
toast.error(`${t("error.something_went_wrong")}`)
|
||||
console.error(err)
|
||||
},
|
||||
() => {
|
||||
invokeAction("workspace.switch.personal")
|
||||
toast.success(`${t("team.deleted")}`)
|
||||
}
|
||||
)
|
||||
)() // Tasks (and TEs) are lazy, so call the function returned
|
||||
}
|
||||
|
||||
defineActionHandler("flyouts.keybinds.toggle", () => {
|
||||
showShortcuts.value = !showShortcuts.value
|
||||
})
|
||||
@@ -20,7 +60,20 @@ defineActionHandler("modals.share.toggle", () => {
|
||||
showShare.value = !showShare.value
|
||||
})
|
||||
|
||||
defineActionHandler("modals.social.toggle", () => {
|
||||
showSocial.value = !showSocial.value
|
||||
})
|
||||
|
||||
defineActionHandler("modals.login.toggle", () => {
|
||||
showLogin.value = !showLogin.value
|
||||
})
|
||||
|
||||
defineActionHandler("flyouts.chat.open", () => {
|
||||
showChat()
|
||||
})
|
||||
|
||||
defineActionHandler("modals.team.delete", ({ teamId }) => {
|
||||
teamID.value = teamId
|
||||
confirmRemove.value = true
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -382,6 +382,22 @@ const settings = ref<any | null>(null)
|
||||
const logout = ref<any | null>(null)
|
||||
const accountActions = ref<any | null>(null)
|
||||
|
||||
defineActionHandler("modals.team.edit", () => {
|
||||
// TODO: Remove this hack
|
||||
setTimeout(() => {
|
||||
handleTeamEdit()
|
||||
}, 100)
|
||||
})
|
||||
|
||||
defineActionHandler("modals.team.invite", () => {
|
||||
if (
|
||||
selectedTeam.value?.myRole === "OWNER" ||
|
||||
selectedTeam.value?.myRole === "EDITOR"
|
||||
) {
|
||||
inviteTeam({ name: selectedTeam.value.name }, selectedTeam.value.id)
|
||||
}
|
||||
})
|
||||
|
||||
defineActionHandler(
|
||||
"user.login",
|
||||
() => {
|
||||
|
||||
@@ -130,13 +130,12 @@
|
||||
@click="nativeShare()"
|
||||
/>
|
||||
</div>
|
||||
<AppShare :show="showShare" @hide-modal="showShare = false" />
|
||||
</template>
|
||||
</HoppSmartModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from "vue"
|
||||
import { watch } from "vue"
|
||||
import IconSidebar from "~icons/lucide/sidebar"
|
||||
import IconSidebarOpen from "~icons/lucide/sidebar-open"
|
||||
import IconBook from "~icons/lucide/book"
|
||||
@@ -151,13 +150,12 @@ import IconUserPlus from "~icons/lucide/user-plus"
|
||||
import IconShare2 from "~icons/lucide/share-2"
|
||||
import IconChevronRight from "~icons/lucide/chevron-right"
|
||||
import { useSetting } from "@composables/settings"
|
||||
import { defineActionHandler } from "~/helpers/actions"
|
||||
import { invokeAction } from "~/helpers/actions"
|
||||
import { showChat } from "@modules/crisp"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
|
||||
const t = useI18n()
|
||||
const navigatorShare = !!navigator.share
|
||||
const showShare = ref(false)
|
||||
|
||||
const ZEN_MODE = useSetting("ZEN_MODE")
|
||||
const EXPAND_NAVIGATION = useSetting("EXPAND_NAVIGATION")
|
||||
@@ -174,10 +172,6 @@ defineProps<{
|
||||
show: boolean
|
||||
}>()
|
||||
|
||||
defineActionHandler("modals.share.toggle", () => {
|
||||
showShare.value = !showShare.value
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "hide-modal"): void
|
||||
}>()
|
||||
@@ -198,7 +192,7 @@ const expandCollection = () => {
|
||||
}
|
||||
|
||||
const expandInvite = () => {
|
||||
showShare.value = true
|
||||
invokeAction("modals.share.toggle")
|
||||
}
|
||||
|
||||
const nativeShare = () => {
|
||||
|
||||
135
packages/hoppscotch-common/src/components/app/Social.vue
Normal file
135
packages/hoppscotch-common/src/components/app/Social.vue
Normal file
@@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<HoppSmartModal
|
||||
v-if="show"
|
||||
dialog
|
||||
:title="t('app.social_links')"
|
||||
@close="hideModal"
|
||||
>
|
||||
<template #body>
|
||||
<div class="flex flex-col space-y-2">
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<a
|
||||
v-for="(platform, index) in platforms"
|
||||
:key="`platform-${index}`"
|
||||
:href="platform.link"
|
||||
target="_blank"
|
||||
class="social-link"
|
||||
tabindex="0"
|
||||
>
|
||||
<component :is="platform.icon" class="w-6 h-6" />
|
||||
<span class="mt-3">
|
||||
{{ platform.name }}
|
||||
</span>
|
||||
</a>
|
||||
<button class="social-link" @click="copyAppLink">
|
||||
<component :is="copyIcon" class="w-6 h-6 text-xl" />
|
||||
<span class="mt-3">
|
||||
{{ t("app.copy") }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<p class="text-secondaryLight">
|
||||
{{ t("app.social_description") }}
|
||||
</p>
|
||||
</template>
|
||||
</HoppSmartModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { refAutoReset } from "@vueuse/core"
|
||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
||||
import IconFacebook from "~icons/brands/facebook"
|
||||
import IconLinkedIn from "~icons/brands/linkedin"
|
||||
import IconReddit from "~icons/brands/reddit"
|
||||
import IconTwitter from "~icons/brands/twitter"
|
||||
import IconCheck from "~icons/lucide/check"
|
||||
import IconCopy from "~icons/lucide/copy"
|
||||
import IconGitHub from "~icons/lucide/github"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
defineProps<{
|
||||
show: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "hide-modal"): void
|
||||
}>()
|
||||
|
||||
const url = "https://hoppscotch.io"
|
||||
|
||||
const copyIcon = refAutoReset<typeof IconCopy | typeof IconCheck>(
|
||||
IconCopy,
|
||||
1000
|
||||
)
|
||||
|
||||
const platforms = [
|
||||
{
|
||||
name: "GitHub",
|
||||
icon: IconGitHub,
|
||||
link: `https://hoppscotch.io/github`,
|
||||
},
|
||||
{
|
||||
name: "Twitter",
|
||||
icon: IconTwitter,
|
||||
link: `https://twitter.com/hoppscotch_io`,
|
||||
},
|
||||
{
|
||||
name: "Facebook",
|
||||
icon: IconFacebook,
|
||||
link: `https://www.facebook.com/hoppscotch.io`,
|
||||
},
|
||||
{
|
||||
name: "Reddit",
|
||||
icon: IconReddit,
|
||||
link: `https://www.reddit.com/r/hoppscotch`,
|
||||
},
|
||||
{
|
||||
name: "LinkedIn",
|
||||
icon: IconLinkedIn,
|
||||
link: `https://www.linkedin.com/company/hoppscotch/`,
|
||||
},
|
||||
]
|
||||
|
||||
const copyAppLink = () => {
|
||||
copyToClipboard(url)
|
||||
copyIcon.value = IconCheck
|
||||
toast.success(`${t("state.copied_to_clipboard")}`)
|
||||
}
|
||||
|
||||
const hideModal = () => {
|
||||
emit("hide-modal")
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.social-link {
|
||||
@apply border border-dividerLight;
|
||||
@apply rounded;
|
||||
@apply flex-col flex;
|
||||
@apply p-4;
|
||||
@apply items-center;
|
||||
@apply justify-center;
|
||||
@apply font-semibold;
|
||||
@apply hover: (bg-primaryLight text-secondaryDark);
|
||||
@apply focus: outline-none;
|
||||
@apply focus-visible: border-divider;
|
||||
|
||||
svg {
|
||||
@apply opacity-80;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
svg {
|
||||
@apply opacity-100;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -98,6 +98,19 @@ import { UserSpotlightSearcherService } from "~/services/spotlight/searchers/use
|
||||
import { NavigationSpotlightSearcherService } from "~/services/spotlight/searchers/navigation.searcher"
|
||||
import { SettingsSpotlightSearcherService } from "~/services/spotlight/searchers/settings.searcher"
|
||||
import { CollectionsSpotlightSearcherService } from "~/services/spotlight/searchers/collections.searcher"
|
||||
import { MiscellaneousSpotlightSearcherService } from "~/services/spotlight/searchers/miscellaneous.searcher"
|
||||
import { TabSpotlightSearcherService } from "~/services/spotlight/searchers/tab.searcher"
|
||||
import { GeneralSpotlightSearcherService } from "~/services/spotlight/searchers/general.searcher"
|
||||
import { ResponseSpotlightSearcherService } from "~/services/spotlight/searchers/response.searcher"
|
||||
import { RequestSpotlightSearcherService } from "~/services/spotlight/searchers/request.searcher"
|
||||
import {
|
||||
EnvironmentsSpotlightSearcherService,
|
||||
SwitchEnvSpotlightSearcherService,
|
||||
} from "~/services/spotlight/searchers/environment.searcher"
|
||||
import {
|
||||
SwitchWorkspaceSpotlightSearcherService,
|
||||
WorkspaceSpotlightSearcherService,
|
||||
} from "~/services/spotlight/searchers/workspace.searcher"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
@@ -116,6 +129,15 @@ useService(UserSpotlightSearcherService)
|
||||
useService(NavigationSpotlightSearcherService)
|
||||
useService(SettingsSpotlightSearcherService)
|
||||
useService(CollectionsSpotlightSearcherService)
|
||||
useService(MiscellaneousSpotlightSearcherService)
|
||||
useService(TabSpotlightSearcherService)
|
||||
useService(GeneralSpotlightSearcherService)
|
||||
useService(ResponseSpotlightSearcherService)
|
||||
useService(RequestSpotlightSearcherService)
|
||||
useService(EnvironmentsSpotlightSearcherService)
|
||||
useService(SwitchEnvSpotlightSearcherService)
|
||||
useService(WorkspaceSpotlightSearcherService)
|
||||
useService(SwitchWorkspaceSpotlightSearcherService)
|
||||
|
||||
const search = ref("")
|
||||
|
||||
@@ -242,3 +264,4 @@ function newUseArrowKeysForNavigation() {
|
||||
return { selectedEntry }
|
||||
}
|
||||
</script>
|
||||
~/services/spotlight/searchers/workspace.searcher
|
||||
|
||||
@@ -198,6 +198,11 @@ const resetSelectedData = () => {
|
||||
editingEnvironmentIndex.value = null
|
||||
}
|
||||
|
||||
defineActionHandler("modals.environment.new", () => {
|
||||
action.value = "new"
|
||||
showModalDetails.value = true
|
||||
})
|
||||
|
||||
defineActionHandler(
|
||||
"modals.my.environment.edit",
|
||||
({ envName, variableName }) => {
|
||||
|
||||
@@ -158,5 +158,7 @@ const duplicateEnvironments = () => {
|
||||
cloneDeep(getGlobalVariables())
|
||||
)
|
||||
} else duplicateEnvironment(props.environmentIndex)
|
||||
|
||||
toast.success(`${t("environment.duplicated")}`)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -154,7 +154,7 @@ const duplicateEnvironments = () => {
|
||||
toast.error(`${getErrorMessage(err)}`)
|
||||
},
|
||||
() => {
|
||||
toast.success(`${t("team_environment.duplicate")}`)
|
||||
toast.success(`${t("environment.duplicated")}`)
|
||||
}
|
||||
)
|
||||
)()
|
||||
|
||||
@@ -630,6 +630,13 @@ defineActionHandler("request.method.put", () => updateMethod("PUT"))
|
||||
defineActionHandler("request.method.delete", () => updateMethod("DELETE"))
|
||||
defineActionHandler("request.method.head", () => updateMethod("HEAD"))
|
||||
|
||||
defineActionHandler("request.import-curl", () => {
|
||||
showCurlImportModal.value = true
|
||||
})
|
||||
defineActionHandler("request.show-code", () => {
|
||||
showCodegenModal.value = true
|
||||
})
|
||||
|
||||
const isCustomMethod = computed(() => {
|
||||
return (
|
||||
tab.value.document.request.method === "CUSTOM" ||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<HoppSmartTabs
|
||||
v-model="selectedRealtimeTab"
|
||||
v-model="selectedOptionsTab"
|
||||
styles="sticky overflow-x-auto flex-shrink-0 bg-primary top-upperMobilePrimaryStickyFold sm:top-upperPrimaryStickyFold z-10"
|
||||
render-inactive-tabs
|
||||
>
|
||||
@@ -56,12 +56,15 @@ import { useI18n } from "@composables/i18n"
|
||||
import { HoppRESTRequest } from "@hoppscotch/data"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
import { computed, ref } from "vue"
|
||||
import { defineActionHandler } from "~/helpers/actions"
|
||||
|
||||
export type RequestOptionTabs =
|
||||
| "params"
|
||||
| "bodyParams"
|
||||
| "headers"
|
||||
| "authorization"
|
||||
| "preRequestScript"
|
||||
| "tests"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
@@ -73,10 +76,10 @@ const emit = defineEmits<{
|
||||
|
||||
const request = useVModel(props, "modelValue", emit)
|
||||
|
||||
const selectedRealtimeTab = ref<RequestOptionTabs>("params")
|
||||
const selectedOptionsTab = ref<RequestOptionTabs>("params")
|
||||
|
||||
const changeTab = (e: RequestOptionTabs) => {
|
||||
selectedRealtimeTab.value = e
|
||||
selectedOptionsTab.value = e
|
||||
}
|
||||
|
||||
const newActiveParamsCount$ = computed(() => {
|
||||
@@ -96,4 +99,8 @@ const newActiveHeadersCount$ = computed(() => {
|
||||
if (e === 0) return null
|
||||
return `${e}`
|
||||
})
|
||||
|
||||
defineActionHandler("request.open-tab", ({ tab }) => {
|
||||
selectedOptionsTab.value = tab
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -82,6 +82,7 @@ import { changeWorkspace, workspaceStatus$ } from "~/newstore/workspace"
|
||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
||||
import IconDone from "~icons/lucide/check"
|
||||
import { useLocalState } from "~/newstore/localstate"
|
||||
import { defineActionHandler } from "~/helpers/actions"
|
||||
|
||||
const t = useI18n()
|
||||
const colorMode = useColorMode()
|
||||
@@ -154,4 +155,14 @@ const displayModalAdd = (shouldDisplay: boolean) => {
|
||||
showModalAdd.value = shouldDisplay
|
||||
teamListadapter.fetchList()
|
||||
}
|
||||
|
||||
defineActionHandler("modals.team.new", () => {
|
||||
displayModalAdd(true)
|
||||
})
|
||||
|
||||
defineActionHandler("workspace.switch.personal", switchToPersonalWorkspace)
|
||||
defineActionHandler("workspace.switch", ({ teamId }) => {
|
||||
const team = myTeams.value.find((t) => t.id === teamId)
|
||||
if (team) switchToTeamWorkspace(team)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Ref, onBeforeUnmount, onMounted, watch } from "vue"
|
||||
import { BehaviorSubject } from "rxjs"
|
||||
import { HoppRESTDocument } from "./rest/document"
|
||||
import { HoppGQLRequest, HoppRESTRequest } from "@hoppscotch/data"
|
||||
import { RequestOptionTabs } from "~/components/http/RequestOptions.vue"
|
||||
|
||||
export type HoppAction =
|
||||
| "contextmenu.open" // Send/Cancel a Hoppscotch Request
|
||||
@@ -14,6 +15,7 @@ export type HoppAction =
|
||||
| "request.copy-link" // Copy Request Link
|
||||
| "request.save" // Save to Collections
|
||||
| "request.save-as" // Save As
|
||||
| "rest.request.rename" // Rename
|
||||
| "request.method.next" // Select Next Method
|
||||
| "request.method.prev" // Select Previous Method
|
||||
| "request.method.get" // Select GET Method
|
||||
@@ -21,13 +23,22 @@ export type HoppAction =
|
||||
| "request.method.post" // Select POST Method
|
||||
| "request.method.put" // Select PUT Method
|
||||
| "request.method.delete" // Select DELETE Method
|
||||
| "request.import-curl" // Import cURL
|
||||
| "request.show-code" // Show generated code
|
||||
| "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.my.environment.edit" // Edit current personal environment
|
||||
| "modals.team.environment.edit" // Edit current team environment
|
||||
| "modals.team.new" // Add new team
|
||||
| "modals.team.edit" // Edit selected team
|
||||
| "modals.team.invite" // Invite selected team
|
||||
| "workspace.switch.personal" // Switch to personal workspace
|
||||
| "navigation.jump.rest" // Jump to REST page
|
||||
| "navigation.jump.graphql" // Jump to GraphQL page
|
||||
| "navigation.jump.realtime" // Jump to realtime page
|
||||
@@ -73,6 +84,12 @@ type HoppActionArgsMap = {
|
||||
envName: string
|
||||
variableName?: string
|
||||
}
|
||||
"modals.team.delete": {
|
||||
teamId: string
|
||||
}
|
||||
"workspace.switch": {
|
||||
teamId: string
|
||||
}
|
||||
"rest.request.open": {
|
||||
doc: HoppRESTDocument
|
||||
}
|
||||
@@ -85,6 +102,10 @@ type HoppActionArgsMap = {
|
||||
requestType: "gql"
|
||||
request: HoppGQLRequest
|
||||
}
|
||||
"request.open-tab": {
|
||||
tab: RequestOptionTabs
|
||||
}
|
||||
|
||||
"gql.request.open": {
|
||||
request: HoppGQLRequest
|
||||
}
|
||||
|
||||
@@ -458,6 +458,13 @@ defineActionHandler("rest.request.open", ({ doc }) => {
|
||||
createNewTab(doc)
|
||||
})
|
||||
|
||||
defineActionHandler("rest.request.rename", () => {
|
||||
// TODO: Fix this hack to open the modal
|
||||
setTimeout(() => {
|
||||
openReqRenameModal()
|
||||
}, 100)
|
||||
})
|
||||
|
||||
const inspectionService = useService(InspectionService)
|
||||
useService(HeaderInspectorService)
|
||||
useService(EnvironmentInspectorService)
|
||||
|
||||
@@ -0,0 +1,356 @@
|
||||
import {
|
||||
Component,
|
||||
Ref,
|
||||
computed,
|
||||
effectScope,
|
||||
markRaw,
|
||||
reactive,
|
||||
ref,
|
||||
watch,
|
||||
} from "vue"
|
||||
import { activeActions$, invokeAction } from "~/helpers/actions"
|
||||
import { getI18n } from "~/modules/i18n"
|
||||
import {
|
||||
SpotlightSearcher,
|
||||
SpotlightSearcherResult,
|
||||
SpotlightSearcherSessionState,
|
||||
SpotlightService,
|
||||
} from ".."
|
||||
import {
|
||||
SearchResult,
|
||||
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 IconLayers from "~icons/lucide/layers"
|
||||
|
||||
import { useStreamStatic } from "~/composables/stream"
|
||||
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
|
||||
alternates: string[]
|
||||
icon: object | Component
|
||||
excludeFromSearch?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* This searcher is responsible for providing environments related actions on the spotlight results.
|
||||
*
|
||||
* NOTE: Initializing this service registers it as a searcher with the Spotlight Service.
|
||||
*/
|
||||
export class EnvironmentsSpotlightSearcherService extends StaticSpotlightSearcherService<Doc> {
|
||||
public static readonly ID = "ENVIRONMENTS_SPOTLIGHT_SEARCHER_SERVICE"
|
||||
|
||||
private t = getI18n()
|
||||
|
||||
public readonly searcherID = "environments"
|
||||
public searcherSectionTitle = this.t("spotlight.environments.title")
|
||||
|
||||
private readonly spotlight = this.bind(SpotlightService)
|
||||
|
||||
private selectedEnvIndex = useStreamStatic(
|
||||
selectedEnvironmentIndex$,
|
||||
null,
|
||||
() => {
|
||||
/* noop */
|
||||
}
|
||||
)[0]
|
||||
|
||||
private selectedEnv = useStreamStatic(currentEnvironment$, null, () => {
|
||||
/* noop */
|
||||
})[0]
|
||||
|
||||
private hasSelectedEnv = computed(
|
||||
() => this.selectedEnvIndex.value?.type !== "NO_ENV_SELECTED"
|
||||
)
|
||||
|
||||
private documents: Record<string, Doc> = reactive({
|
||||
new_environment: {
|
||||
text: this.t("spotlight.environments.new"),
|
||||
alternates: ["new", "environment"],
|
||||
icon: markRaw(IconLayers),
|
||||
},
|
||||
new_environment_variable: {
|
||||
text: this.t("spotlight.environments.new_variable"),
|
||||
alternates: ["new", "environment", "variable"],
|
||||
icon: markRaw(IconLayers),
|
||||
},
|
||||
edit_selected_env: {
|
||||
text: this.t("spotlight.environments.edit"),
|
||||
alternates: ["edit", "environment"],
|
||||
icon: markRaw(IconEdit),
|
||||
excludeFromSearch: computed(() => !this.hasSelectedEnv.value),
|
||||
},
|
||||
delete_selected_env: {
|
||||
text: this.t("spotlight.environments.delete"),
|
||||
alternates: ["delete", "environment"],
|
||||
icon: markRaw(IconTrash2),
|
||||
excludeFromSearch: computed(() => !this.hasSelectedEnv.value),
|
||||
},
|
||||
duplicate_selected_env: {
|
||||
text: this.t("spotlight.environments.duplicate"),
|
||||
alternates: ["duplicate", "environment"],
|
||||
icon: markRaw(IconCopy),
|
||||
excludeFromSearch: computed(() => !this.hasSelectedEnv.value),
|
||||
},
|
||||
edit_global_env: {
|
||||
text: this.t("spotlight.environments.edit_global"),
|
||||
alternates: ["edit", "global", "environment"],
|
||||
icon: markRaw(IconEdit),
|
||||
},
|
||||
duplicate_global_env: {
|
||||
text: this.t("spotlight.environments.duplicate_global"),
|
||||
alternates: ["duplicate", "global", "environment"],
|
||||
icon: markRaw(IconCopy),
|
||||
},
|
||||
})
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
searchFields: ["text", "alternates"],
|
||||
fieldWeights: {
|
||||
text: 2,
|
||||
alternates: 1,
|
||||
},
|
||||
})
|
||||
|
||||
this.setDocuments(this.documents)
|
||||
this.spotlight.registerSearcher(this)
|
||||
}
|
||||
|
||||
protected getSearcherResultForSearchResult(
|
||||
result: SearchResult<Doc>
|
||||
): SpotlightSearcherResult {
|
||||
return {
|
||||
id: result.id,
|
||||
icon: result.doc.icon,
|
||||
text: { type: "text", text: result.doc.text },
|
||||
score: result.score,
|
||||
}
|
||||
}
|
||||
|
||||
private getSelectedText() {
|
||||
const selection = window.getSelection()
|
||||
return selection?.toString() ?? ""
|
||||
}
|
||||
|
||||
duplicateGlobalEnv() {
|
||||
createEnvironment(
|
||||
`Global - ${this.t("action.duplicate")}`,
|
||||
cloneDeep(getGlobalVariables())
|
||||
)
|
||||
// this.toast.success(`${t("environment.duplicated")}`)
|
||||
}
|
||||
|
||||
duplicateSelectedEnv() {
|
||||
if (this.selectedEnvIndex.value?.type === "NO_ENV_SELECTED") return
|
||||
|
||||
if (this.selectedEnvIndex.value?.type === "MY_ENV") {
|
||||
duplicateEnvironment(this.selectedEnvIndex.value.index)
|
||||
// this.toast.success(`${t("environment.duplicated")}`)
|
||||
}
|
||||
|
||||
if (this.selectedEnvIndex.value?.type === "TEAM_ENV") {
|
||||
pipe(
|
||||
deleteTeamEnvironment(this.selectedEnvIndex.value.teamEnvID),
|
||||
TE.match(
|
||||
(err: GQLError<string>) => {
|
||||
console.error(err)
|
||||
},
|
||||
() => {
|
||||
// this.toast.success(`${this.t("environment.duplicated")}`)
|
||||
}
|
||||
)
|
||||
)()
|
||||
}
|
||||
}
|
||||
|
||||
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<string>) => {
|
||||
console.error(err)
|
||||
},
|
||||
() => {
|
||||
// this.toast.success(`${this.t("team_environment.deleted")}`)
|
||||
}
|
||||
)
|
||||
)()
|
||||
}
|
||||
}
|
||||
|
||||
public onDocSelected(id: string): void {
|
||||
switch (id) {
|
||||
case "new_environment":
|
||||
invokeAction(`modals.environment.new`)
|
||||
break
|
||||
case "new_environment_variable":
|
||||
invokeAction(`modals.environment.add`, {
|
||||
envName: "",
|
||||
variableName: this.getSelectedText(),
|
||||
})
|
||||
break
|
||||
case "edit_selected_env":
|
||||
if (this.selectedEnv.value)
|
||||
invokeAction(`modals.my.environment.edit`, {
|
||||
envName: this.selectedEnv.value.name,
|
||||
})
|
||||
break
|
||||
case "delete_selected_env":
|
||||
this.removeSelectedEnvironment()
|
||||
break
|
||||
case "duplicate_selected_env":
|
||||
this.duplicateSelectedEnv()
|
||||
break
|
||||
case "edit_global_env":
|
||||
invokeAction(`modals.my.environment.edit`, {
|
||||
envName: "Global",
|
||||
})
|
||||
break
|
||||
case "duplicate_global_env":
|
||||
this.duplicateGlobalEnv()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This searcher is responsible for searching through the environment.
|
||||
* And switching between them.
|
||||
*/
|
||||
export class SwitchEnvSpotlightSearcherService
|
||||
extends Service
|
||||
implements SpotlightSearcher
|
||||
{
|
||||
public static readonly ID = "SWITCH_ENV_SPOTLIGHT_SEARCHER_SERVICE"
|
||||
|
||||
private t = getI18n()
|
||||
|
||||
public searcherID = "switch_env"
|
||||
public searcherSectionTitle = this.t("tab.environments")
|
||||
|
||||
private readonly spotlight = this.bind(SpotlightService)
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
|
||||
this.spotlight.registerSearcher(this)
|
||||
}
|
||||
|
||||
private environmentSearchable = useStreamStatic(
|
||||
activeActions$.pipe(
|
||||
map((actions) => actions.includes("modals.environment.add"))
|
||||
),
|
||||
activeActions$.value.includes("modals.environment.add"),
|
||||
() => {
|
||||
/* noop */
|
||||
}
|
||||
)[0]
|
||||
|
||||
createSearchSession(
|
||||
query: Readonly<Ref<string>>
|
||||
): [Ref<SpotlightSearcherSessionState>, () => void] {
|
||||
const loading = ref(false)
|
||||
const results = ref<SpotlightSearcherResult[]>([])
|
||||
|
||||
const minisearch = new MiniSearch({
|
||||
fields: ["name"],
|
||||
storeFields: ["name"],
|
||||
})
|
||||
|
||||
if (this.environmentSearchable.value) {
|
||||
minisearch.addAll(
|
||||
environmentsStore.value.environments.map((entry, index) => {
|
||||
return {
|
||||
id: `environment-${index}`,
|
||||
name: entry.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(IconLayers),
|
||||
score: x.score,
|
||||
text: {
|
||||
type: "text",
|
||||
text: [this.t("environment.set"), x.name],
|
||||
},
|
||||
}
|
||||
})
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
})
|
||||
|
||||
const onSessionEnd = () => {
|
||||
scopeHandle.stop()
|
||||
minisearch.removeAll()
|
||||
}
|
||||
|
||||
const resultObj = computed<SpotlightSearcherSessionState>(() => ({
|
||||
loading: loading.value,
|
||||
results: results.value,
|
||||
}))
|
||||
|
||||
return [resultObj, onSessionEnd]
|
||||
}
|
||||
|
||||
onResultSelect(result: SpotlightSearcherResult): void {
|
||||
const selectedEnvIndex = Number(result.id.split("-")[1])
|
||||
setSelectedEnvironmentIndex({
|
||||
type: "MY_ENV",
|
||||
index: selectedEnvIndex,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
import { Component, markRaw, reactive } from "vue"
|
||||
import { invokeAction } from "~/helpers/actions"
|
||||
import { getI18n } from "~/modules/i18n"
|
||||
import { SpotlightSearcherResult, SpotlightService } from ".."
|
||||
import {
|
||||
SearchResult,
|
||||
StaticSpotlightSearcherService,
|
||||
} from "./base/static.searcher"
|
||||
|
||||
import IconBook from "~icons/lucide/book"
|
||||
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
|
||||
alternates: string[]
|
||||
icon: object | Component
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* This searcher is responsible for providing general related actions on the spotlight results.
|
||||
*
|
||||
* NOTE: Initializing this service registers it as a searcher with the Spotlight Service.
|
||||
*/
|
||||
export class GeneralSpotlightSearcherService extends StaticSpotlightSearcherService<Doc> {
|
||||
public static readonly ID = "GENERAL_SPOTLIGHT_SEARCHER_SERVICE"
|
||||
|
||||
private t = getI18n()
|
||||
|
||||
public readonly searcherID = "general"
|
||||
public searcherSectionTitle = this.t("spotlight.general.title")
|
||||
|
||||
private readonly spotlight = this.bind(SpotlightService)
|
||||
|
||||
private documents: Record<string, Doc> = reactive({
|
||||
open_help: {
|
||||
text: this.t("spotlight.general.help_menu"),
|
||||
alternates: ["help", "hoppscotch"],
|
||||
icon: markRaw(IconLifeBuoy),
|
||||
},
|
||||
chat_with_support: {
|
||||
text: this.t("spotlight.general.chat"),
|
||||
alternates: ["chat", "support", "hoppscotch"],
|
||||
icon: markRaw(IconMessageCircle),
|
||||
},
|
||||
open_docs: {
|
||||
text: this.t("spotlight.general.open_docs"),
|
||||
alternates: ["docs", "documentation", "hoppscotch"],
|
||||
icon: markRaw(IconBook),
|
||||
},
|
||||
open_keybindings: {
|
||||
text: this.t("spotlight.general.open_keybindings"),
|
||||
alternates: ["key", "shortcuts", "binding"],
|
||||
icon: markRaw(IconZap),
|
||||
},
|
||||
social_links: {
|
||||
text: this.t("spotlight.general.social"),
|
||||
alternates: ["social", "github", "binding"],
|
||||
icon: markRaw(IconGithub),
|
||||
},
|
||||
})
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
searchFields: ["text", "alternates"],
|
||||
fieldWeights: {
|
||||
text: 2,
|
||||
alternates: 1,
|
||||
},
|
||||
})
|
||||
|
||||
this.setDocuments(this.documents)
|
||||
this.spotlight.registerSearcher(this)
|
||||
}
|
||||
|
||||
protected getSearcherResultForSearchResult(
|
||||
result: SearchResult<Doc>
|
||||
): SpotlightSearcherResult {
|
||||
return {
|
||||
id: result.id,
|
||||
icon: result.doc.icon,
|
||||
text: { type: "text", text: result.doc.text },
|
||||
score: result.score,
|
||||
}
|
||||
}
|
||||
|
||||
private openDocs() {
|
||||
const url = "https://docs.hoppscotch.io"
|
||||
window.open(url, "_blank")
|
||||
}
|
||||
|
||||
public onDocSelected(id: string): void {
|
||||
switch (id) {
|
||||
case "open_help":
|
||||
invokeAction("modals.support.toggle")
|
||||
break
|
||||
case "chat_with_support":
|
||||
invokeAction("flyouts.chat.open")
|
||||
break
|
||||
case "open_docs":
|
||||
this.openDocs()
|
||||
break
|
||||
case "open_keybindings":
|
||||
invokeAction("flyouts.keybinds.toggle")
|
||||
break
|
||||
case "social_links":
|
||||
invokeAction("modals.social.toggle")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { Component, markRaw, reactive } from "vue"
|
||||
import { invokeAction } from "~/helpers/actions"
|
||||
import { getI18n } from "~/modules/i18n"
|
||||
import { SpotlightSearcherResult, SpotlightService } from ".."
|
||||
import {
|
||||
SearchResult,
|
||||
StaticSpotlightSearcherService,
|
||||
} from "./base/static.searcher"
|
||||
|
||||
import IconShare from "~icons/lucide/share"
|
||||
|
||||
type Doc = {
|
||||
text: string
|
||||
alternates: string[]
|
||||
icon: object | Component
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* This searcher is responsible for providing miscellaneous related actions on the spotlight results.
|
||||
*
|
||||
* NOTE: Initializing this service registers it as a searcher with the Spotlight Service.
|
||||
*/
|
||||
export class MiscellaneousSpotlightSearcherService extends StaticSpotlightSearcherService<Doc> {
|
||||
public static readonly ID = "MISCELLANEOUS_SPOTLIGHT_SEARCHER_SERVICE"
|
||||
|
||||
private t = getI18n()
|
||||
|
||||
public readonly searcherID = "miscellaneous"
|
||||
public searcherSectionTitle = this.t("spotlight.miscellaneous.title")
|
||||
|
||||
private readonly spotlight = this.bind(SpotlightService)
|
||||
|
||||
private documents: Record<string, Doc> = reactive({
|
||||
invite_hoppscotch: {
|
||||
text: this.t("spotlight.miscellaneous.invite"),
|
||||
alternates: ["invite", "share", "hoppscotch"],
|
||||
icon: markRaw(IconShare),
|
||||
},
|
||||
})
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
searchFields: ["text", "alternates"],
|
||||
fieldWeights: {
|
||||
text: 2,
|
||||
alternates: 1,
|
||||
},
|
||||
})
|
||||
|
||||
this.setDocuments(this.documents)
|
||||
this.spotlight.registerSearcher(this)
|
||||
}
|
||||
|
||||
protected getSearcherResultForSearchResult(
|
||||
result: SearchResult<Doc>
|
||||
): SpotlightSearcherResult {
|
||||
return {
|
||||
id: result.id,
|
||||
icon: result.doc.icon,
|
||||
text: { type: "text", text: result.doc.text },
|
||||
score: result.score,
|
||||
}
|
||||
}
|
||||
|
||||
public onDocSelected(id: string): void {
|
||||
if (id === "invite_hoppscotch") invokeAction(`modals.share.toggle`)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
import { Component, markRaw, reactive } from "vue"
|
||||
import { invokeAction } from "~/helpers/actions"
|
||||
import { getI18n } from "~/modules/i18n"
|
||||
import { SpotlightSearcherResult, SpotlightService } from ".."
|
||||
import {
|
||||
SearchResult,
|
||||
StaticSpotlightSearcherService,
|
||||
} from "./base/static.searcher"
|
||||
|
||||
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 IconCode2 from "~icons/lucide/code-2"
|
||||
import IconCopy from "~icons/lucide/copy"
|
||||
import IconFileCode from "~icons/lucide/file-code"
|
||||
import IconRename from "~icons/lucide/file-edit"
|
||||
import IconPlay from "~icons/lucide/play"
|
||||
import IconRotateCCW from "~icons/lucide/rotate-ccw"
|
||||
import IconSave from "~icons/lucide/save"
|
||||
|
||||
type Doc = {
|
||||
text: string | string[]
|
||||
alternates: string[]
|
||||
icon: object | Component
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* This searcher is responsible for providing request related actions on the spotlight results.
|
||||
*
|
||||
* NOTE: Initializing this service registers it as a searcher with the Spotlight Service.
|
||||
*/
|
||||
export class RequestSpotlightSearcherService extends StaticSpotlightSearcherService<Doc> {
|
||||
public static readonly ID = "REQUEST_SPOTLIGHT_SEARCHER_SERVICE"
|
||||
|
||||
private t = getI18n()
|
||||
|
||||
public readonly searcherID = "request"
|
||||
public searcherSectionTitle = this.t("shortcut.request.title")
|
||||
|
||||
private readonly spotlight = this.bind(SpotlightService)
|
||||
|
||||
private documents: Record<string, Doc> = reactive({
|
||||
send_request: {
|
||||
text: this.t("shortcut.request.send_request"),
|
||||
alternates: ["request", "send"],
|
||||
icon: markRaw(IconPlay),
|
||||
},
|
||||
save_to_collections: {
|
||||
text: [
|
||||
this.t("request.save_as"),
|
||||
this.t("shortcut.request.save_to_collections"),
|
||||
],
|
||||
alternates: ["save", "collections"],
|
||||
icon: markRaw(IconSave),
|
||||
},
|
||||
save_request: {
|
||||
text: this.t("shortcut.request.save_request"),
|
||||
alternates: ["save", "request"],
|
||||
icon: markRaw(IconSave),
|
||||
},
|
||||
rename_request: {
|
||||
text: this.t("shortcut.request.rename"),
|
||||
alternates: ["rename", "request"],
|
||||
icon: markRaw(IconRename),
|
||||
},
|
||||
copy_request_link: {
|
||||
text: this.t("shortcut.request.copy_request_link"),
|
||||
alternates: ["copy", "link"],
|
||||
icon: markRaw(IconCopy),
|
||||
},
|
||||
reset_request: {
|
||||
text: this.t("shortcut.request.reset_request"),
|
||||
alternates: ["reset", "request"],
|
||||
icon: markRaw(IconRotateCCW),
|
||||
},
|
||||
import_curl: {
|
||||
text: this.t("shortcut.request.import_curl"),
|
||||
alternates: ["import", "curl"],
|
||||
icon: markRaw(IconFileCode),
|
||||
},
|
||||
show_code: {
|
||||
text: this.t("shortcut.request.show_code"),
|
||||
alternates: ["show", "code"],
|
||||
icon: markRaw(IconCode2),
|
||||
},
|
||||
// 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"),
|
||||
alternates: ["get", "method"],
|
||||
icon: markRaw(IconCheck),
|
||||
},
|
||||
head_method: {
|
||||
text: this.t("shortcut.request.head_method"),
|
||||
alternates: ["head", "method"],
|
||||
icon: markRaw(IconCheck),
|
||||
},
|
||||
post_method: {
|
||||
text: this.t("shortcut.request.post_method"),
|
||||
alternates: ["post", "method"],
|
||||
icon: markRaw(IconCheck),
|
||||
},
|
||||
put_method: {
|
||||
text: this.t("shortcut.request.put_method"),
|
||||
alternates: ["put", "method"],
|
||||
icon: markRaw(IconCheck),
|
||||
},
|
||||
delete_method: {
|
||||
text: this.t("shortcut.request.delete_method"),
|
||||
alternates: ["delete", "method"],
|
||||
icon: markRaw(IconCheck),
|
||||
},
|
||||
// Change sub tabs
|
||||
tab_parameters: {
|
||||
text: this.t("spotlight.request.tab_parameters"),
|
||||
alternates: ["parameters", "tab"],
|
||||
icon: markRaw(IconWindow),
|
||||
},
|
||||
tab_body: {
|
||||
text: this.t("spotlight.request.tab_body"),
|
||||
alternates: ["body", "tab"],
|
||||
icon: markRaw(IconWindow),
|
||||
},
|
||||
tab_headers: {
|
||||
text: this.t("spotlight.request.tab_headers"),
|
||||
alternates: ["headers", "tab"],
|
||||
icon: markRaw(IconWindow),
|
||||
},
|
||||
tab_authorization: {
|
||||
text: this.t("spotlight.request.tab_authorization"),
|
||||
alternates: ["authorization", "tab"],
|
||||
icon: markRaw(IconWindow),
|
||||
},
|
||||
tab_pre_request_script: {
|
||||
text: this.t("spotlight.request.tab_pre_request_script"),
|
||||
alternates: ["pre-request", "script", "tab"],
|
||||
icon: markRaw(IconWindow),
|
||||
},
|
||||
tab_tests: {
|
||||
text: this.t("spotlight.request.tab_tests"),
|
||||
alternates: ["tests", "tab"],
|
||||
icon: markRaw(IconWindow),
|
||||
},
|
||||
})
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
searchFields: ["text", "alternates"],
|
||||
fieldWeights: {
|
||||
text: 2,
|
||||
alternates: 1,
|
||||
},
|
||||
})
|
||||
|
||||
this.setDocuments(this.documents)
|
||||
this.spotlight.registerSearcher(this)
|
||||
}
|
||||
|
||||
protected getSearcherResultForSearchResult(
|
||||
result: SearchResult<Doc>
|
||||
): SpotlightSearcherResult {
|
||||
return {
|
||||
id: result.id,
|
||||
icon: result.doc.icon,
|
||||
text: { type: "text", text: result.doc.text },
|
||||
score: result.score,
|
||||
}
|
||||
}
|
||||
|
||||
private openRequestTab(tab: RequestOptionTabs): void {
|
||||
invokeAction("request.open-tab", {
|
||||
tab,
|
||||
})
|
||||
}
|
||||
|
||||
public onDocSelected(id: string): void {
|
||||
switch (id) {
|
||||
case "send_request":
|
||||
invokeAction("request.send-cancel")
|
||||
break
|
||||
case "save_to_collections":
|
||||
invokeAction("request.save-as", {
|
||||
requestType: "rest",
|
||||
request: currentActiveTab.value?.document.request,
|
||||
})
|
||||
break
|
||||
case "save_request":
|
||||
invokeAction("request.save")
|
||||
break
|
||||
case "rename_request":
|
||||
invokeAction("rest.request.rename")
|
||||
break
|
||||
case "copy_request_link":
|
||||
invokeAction("request.copy-link")
|
||||
break
|
||||
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
|
||||
case "head_method":
|
||||
invokeAction("request.method.head")
|
||||
break
|
||||
case "post_method":
|
||||
invokeAction("request.method.post")
|
||||
break
|
||||
case "put_method":
|
||||
invokeAction("request.method.put")
|
||||
break
|
||||
case "delete_method":
|
||||
invokeAction("request.method.delete")
|
||||
break
|
||||
case "import_curl":
|
||||
invokeAction("request.import-curl")
|
||||
break
|
||||
case "show_code":
|
||||
invokeAction("request.show-code")
|
||||
break
|
||||
case "tab_parameters":
|
||||
this.openRequestTab("params")
|
||||
break
|
||||
case "tab_body":
|
||||
this.openRequestTab("bodyParams")
|
||||
break
|
||||
case "tab_headers":
|
||||
this.openRequestTab("headers")
|
||||
break
|
||||
case "tab_authorization":
|
||||
this.openRequestTab("authorization")
|
||||
break
|
||||
case "tab_pre_request_script":
|
||||
this.openRequestTab("preRequestScript")
|
||||
break
|
||||
case "tab_tests":
|
||||
this.openRequestTab("tests")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import { Component, computed, markRaw, reactive } from "vue"
|
||||
import { activeActions$, invokeAction } from "~/helpers/actions"
|
||||
import { getI18n } from "~/modules/i18n"
|
||||
import { SpotlightSearcherResult, SpotlightService } from ".."
|
||||
import {
|
||||
SearchResult,
|
||||
StaticSpotlightSearcherService,
|
||||
} from "./base/static.searcher"
|
||||
|
||||
import IconDownload from "~icons/lucide/download"
|
||||
import IconCopy from "~icons/lucide/copy"
|
||||
import { map } from "rxjs"
|
||||
import { useStreamStatic } from "~/composables/stream"
|
||||
|
||||
type Doc = {
|
||||
text: string
|
||||
alternates: string[]
|
||||
icon: object | Component
|
||||
excludeFromSearch?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* This searcher is responsible for providing response related actions on the spotlight results.
|
||||
*
|
||||
* NOTE: Initializing this service registers it as a searcher with the Spotlight Service.
|
||||
*/
|
||||
export class ResponseSpotlightSearcherService extends StaticSpotlightSearcherService<Doc> {
|
||||
public static readonly ID = "RESPONSE_SPOTLIGHT_SEARCHER_SERVICE"
|
||||
|
||||
private t = getI18n()
|
||||
|
||||
public readonly searcherID = "response"
|
||||
public searcherSectionTitle = this.t("spotlight.response.title")
|
||||
|
||||
private readonly spotlight = this.bind(SpotlightService)
|
||||
|
||||
private copyResponseActionEnabled = useStreamStatic(
|
||||
activeActions$.pipe(map((actions) => actions.includes("response.copy"))),
|
||||
activeActions$.value.includes("response.copy"),
|
||||
() => {
|
||||
/* noop */
|
||||
}
|
||||
)[0]
|
||||
|
||||
private downloadResponseActionEnabled = useStreamStatic(
|
||||
activeActions$.pipe(
|
||||
map((actions) => actions.includes("response.file.download"))
|
||||
),
|
||||
activeActions$.value.includes("response.file.download"),
|
||||
() => {
|
||||
/* noop */
|
||||
}
|
||||
)[0]
|
||||
|
||||
private documents: Record<string, Doc> = reactive({
|
||||
copy_response: {
|
||||
text: this.t("spotlight.response.copy"),
|
||||
alternates: ["copy", "response"],
|
||||
icon: markRaw(IconCopy),
|
||||
excludeFromSearch: computed(() => !this.copyResponseActionEnabled.value),
|
||||
},
|
||||
download_response: {
|
||||
text: this.t("spotlight.response.download"),
|
||||
alternates: ["download", "response"],
|
||||
icon: markRaw(IconDownload),
|
||||
excludeFromSearch: computed(
|
||||
() => !this.downloadResponseActionEnabled.value
|
||||
),
|
||||
},
|
||||
})
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
searchFields: ["text", "alternates"],
|
||||
fieldWeights: {
|
||||
text: 2,
|
||||
alternates: 1,
|
||||
},
|
||||
})
|
||||
|
||||
this.setDocuments(this.documents)
|
||||
this.spotlight.registerSearcher(this)
|
||||
}
|
||||
|
||||
protected getSearcherResultForSearchResult(
|
||||
result: SearchResult<Doc>
|
||||
): SpotlightSearcherResult {
|
||||
return {
|
||||
id: result.id,
|
||||
icon: result.doc.icon,
|
||||
text: { type: "text", text: result.doc.text },
|
||||
score: result.score,
|
||||
}
|
||||
}
|
||||
|
||||
public onDocSelected(id: string): void {
|
||||
if (id === "copy_response") invokeAction(`response.copy`)
|
||||
if (id === "download_response") invokeAction(`response.file.download`)
|
||||
}
|
||||
}
|
||||
@@ -76,7 +76,10 @@ export class SettingsSpotlightSearcherService extends StaticSpotlightSearcherSer
|
||||
icon: markRaw(IconMoon),
|
||||
},
|
||||
font_size_sm: {
|
||||
text: this.t("spotlight.font.size_sm"),
|
||||
text: [
|
||||
this.t("settings.font_size"),
|
||||
this.t("spotlight.settings.font.size_sm"),
|
||||
],
|
||||
onClick: () => {
|
||||
console.log("clicked")
|
||||
},
|
||||
@@ -90,7 +93,10 @@ export class SettingsSpotlightSearcherService extends StaticSpotlightSearcherSer
|
||||
icon: markRaw(IconType),
|
||||
},
|
||||
font_size_md: {
|
||||
text: this.t("spotlight.font.size_md"),
|
||||
text: [
|
||||
this.t("settings.font_size"),
|
||||
this.t("spotlight.settings.font.size_md"),
|
||||
],
|
||||
excludeFromSearch: computed(() => this.activeFontSize.value === "medium"),
|
||||
alternates: [
|
||||
"font size",
|
||||
@@ -101,7 +107,10 @@ export class SettingsSpotlightSearcherService extends StaticSpotlightSearcherSer
|
||||
icon: markRaw(IconType),
|
||||
},
|
||||
font_size_lg: {
|
||||
text: this.t("spotlight.font.size_lg"),
|
||||
text: [
|
||||
this.t("settings.font_size"),
|
||||
this.t("spotlight.settings.font.size_lg"),
|
||||
],
|
||||
excludeFromSearch: computed(() => this.activeFontSize.value === "large"),
|
||||
alternates: [
|
||||
"font size",
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import { Component, markRaw, reactive } from "vue"
|
||||
import { getI18n } from "~/modules/i18n"
|
||||
import { SpotlightSearcherResult, SpotlightService } from ".."
|
||||
import {
|
||||
SearchResult,
|
||||
StaticSpotlightSearcherService,
|
||||
} from "./base/static.searcher"
|
||||
|
||||
import {
|
||||
closeOtherTabs,
|
||||
closeTab,
|
||||
createNewTab,
|
||||
currentTabID,
|
||||
} from "~/helpers/rest/tab"
|
||||
import IconWindow from "~icons/lucide/app-window"
|
||||
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
||||
|
||||
type Doc = {
|
||||
text: string
|
||||
alternates: string[]
|
||||
icon: object | Component
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* This searcher is responsible for providing REST Tab related actions on the spotlight results.
|
||||
*
|
||||
* NOTE: Initializing this service registers it as a searcher with the Spotlight Service.
|
||||
*/
|
||||
export class TabSpotlightSearcherService extends StaticSpotlightSearcherService<Doc> {
|
||||
public static readonly ID = "TAB_SPOTLIGHT_SEARCHER_SERVICE"
|
||||
|
||||
private t = getI18n()
|
||||
|
||||
public readonly searcherID = "tab"
|
||||
public searcherSectionTitle = this.t("spotlight.tab.title")
|
||||
|
||||
private readonly spotlight = this.bind(SpotlightService)
|
||||
|
||||
private documents: Record<string, Doc> = reactive({
|
||||
close_current_tab: {
|
||||
text: this.t("spotlight.tab.close_current"),
|
||||
alternates: ["tab", "close", "close tab"],
|
||||
icon: markRaw(IconWindow),
|
||||
},
|
||||
close_others_tab: {
|
||||
text: this.t("spotlight.tab.close_others"),
|
||||
alternates: ["tab", "close", "close all"],
|
||||
icon: markRaw(IconWindow),
|
||||
},
|
||||
open_new_tab: {
|
||||
text: this.t("spotlight.tab.new_tab"),
|
||||
alternates: ["tab", "new", "open tab"],
|
||||
icon: markRaw(IconWindow),
|
||||
},
|
||||
})
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
searchFields: ["text", "alternates"],
|
||||
fieldWeights: {
|
||||
text: 2,
|
||||
alternates: 1,
|
||||
},
|
||||
})
|
||||
|
||||
this.setDocuments(this.documents)
|
||||
this.spotlight.registerSearcher(this)
|
||||
}
|
||||
|
||||
protected getSearcherResultForSearchResult(
|
||||
result: SearchResult<Doc>
|
||||
): SpotlightSearcherResult {
|
||||
return {
|
||||
id: result.id,
|
||||
icon: result.doc.icon,
|
||||
text: { type: "text", text: result.doc.text },
|
||||
score: result.score,
|
||||
}
|
||||
}
|
||||
|
||||
public onDocSelected(id: string): void {
|
||||
if (id === "close_current_tab") closeTab(currentTabID.value)
|
||||
if (id === "close_others_tab") closeOtherTabs(currentTabID.value)
|
||||
if (id === "open_new_tab")
|
||||
createNewTab({
|
||||
request: getDefaultRESTRequest(),
|
||||
isDirty: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
import {
|
||||
Component,
|
||||
Ref,
|
||||
computed,
|
||||
effectScope,
|
||||
markRaw,
|
||||
reactive,
|
||||
ref,
|
||||
watch,
|
||||
} from "vue"
|
||||
import { invokeAction } from "~/helpers/actions"
|
||||
import { getI18n } from "~/modules/i18n"
|
||||
import {
|
||||
SpotlightSearcher,
|
||||
SpotlightSearcherResult,
|
||||
SpotlightSearcherSessionState,
|
||||
SpotlightService,
|
||||
} from ".."
|
||||
import {
|
||||
SearchResult,
|
||||
StaticSpotlightSearcherService,
|
||||
} from "./base/static.searcher"
|
||||
|
||||
import { Service } from "dioc"
|
||||
import * as E from "fp-ts/Either"
|
||||
import MiniSearch from "minisearch"
|
||||
import { useStreamStatic } from "~/composables/stream"
|
||||
import { runGQLQuery } from "~/helpers/backend/GQLClient"
|
||||
import { GetMyTeamsDocument, GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
||||
import { workspaceStatus$ } from "~/newstore/workspace"
|
||||
import { platform } from "~/platform"
|
||||
import IconEdit from "~icons/lucide/edit"
|
||||
import IconTrash2 from "~icons/lucide/trash-2"
|
||||
import IconUser from "~icons/lucide/user"
|
||||
import IconUserPlus from "~icons/lucide/user-plus"
|
||||
import IconUsers from "~icons/lucide/users"
|
||||
|
||||
type Doc = {
|
||||
text: string
|
||||
alternates: string[]
|
||||
icon: object | Component
|
||||
excludeFromSearch?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* This searcher is responsible for providing team related actions on the spotlight results.
|
||||
*
|
||||
* NOTE: Initializing this service registers it as a searcher with the Spotlight Service.
|
||||
*/
|
||||
export class WorkspaceSpotlightSearcherService extends StaticSpotlightSearcherService<Doc> {
|
||||
public static readonly ID = "WORKSPACE_SPOTLIGHT_SEARCHER_SERVICE"
|
||||
|
||||
private t = getI18n()
|
||||
|
||||
public readonly searcherID = "workspace"
|
||||
public searcherSectionTitle = this.t("spotlight.workspace.title")
|
||||
|
||||
private readonly spotlight = this.bind(SpotlightService)
|
||||
|
||||
private workspace = useStreamStatic(
|
||||
workspaceStatus$,
|
||||
{ type: "personal" },
|
||||
() => {
|
||||
/* noop */
|
||||
}
|
||||
)[0]
|
||||
|
||||
private isTeamSelected = computed(
|
||||
() =>
|
||||
this.workspace.value.type === "team" &&
|
||||
this.workspace.value.teamID !== undefined
|
||||
)
|
||||
|
||||
private documents: Record<string, Doc> = reactive({
|
||||
new_team: {
|
||||
text: this.t("spotlight.workspace.new"),
|
||||
alternates: ["new", "team", "workspace"],
|
||||
icon: markRaw(IconUsers),
|
||||
},
|
||||
edit_team: {
|
||||
text: 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"),
|
||||
alternates: ["invite", "members", "workspace"],
|
||||
icon: markRaw(IconUserPlus),
|
||||
excludeFromSearch: computed(() => !this.isTeamSelected.value),
|
||||
},
|
||||
delete_team: {
|
||||
text: 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"),
|
||||
alternates: ["switch", "team", "workspace", "personal"],
|
||||
icon: markRaw(IconUser),
|
||||
excludeFromSearch: computed(() => !this.isTeamSelected.value),
|
||||
},
|
||||
})
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
searchFields: ["text", "alternates"],
|
||||
fieldWeights: {
|
||||
text: 2,
|
||||
alternates: 1,
|
||||
},
|
||||
})
|
||||
|
||||
this.setDocuments(this.documents)
|
||||
this.spotlight.registerSearcher(this)
|
||||
}
|
||||
|
||||
protected getSearcherResultForSearchResult(
|
||||
result: SearchResult<Doc>
|
||||
): SpotlightSearcherResult {
|
||||
return {
|
||||
id: result.id,
|
||||
icon: result.doc.icon,
|
||||
text: { type: "text", text: result.doc.text },
|
||||
score: result.score,
|
||||
}
|
||||
}
|
||||
|
||||
private deleteTeam(): void {
|
||||
if (this.workspace.value.type === "team")
|
||||
invokeAction(`modals.team.delete`, {
|
||||
teamId: this.workspace.value.teamID,
|
||||
})
|
||||
}
|
||||
|
||||
public onDocSelected(id: string): void {
|
||||
if (id === "new_team") invokeAction(`modals.team.new`)
|
||||
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")
|
||||
invokeAction(`workspace.switch.personal`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This searcher is responsible for searching through the environment.
|
||||
* And switching between them.
|
||||
*/
|
||||
export class SwitchWorkspaceSpotlightSearcherService
|
||||
extends Service
|
||||
implements SpotlightSearcher
|
||||
{
|
||||
public static readonly ID = "SWITCH_WORKSPACE_SPOTLIGHT_SEARCHER_SERVICE"
|
||||
|
||||
private t = getI18n()
|
||||
|
||||
public searcherID = "switch_workspace"
|
||||
public searcherSectionTitle = this.t("workspace.title")
|
||||
|
||||
private readonly spotlight = this.bind(SpotlightService)
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
|
||||
this.spotlight.registerSearcher(this)
|
||||
}
|
||||
|
||||
private fetchMyTeams(): Promise<GetMyTeamsQuery["myTeams"]> {
|
||||
return new Promise(async (resolve) => {
|
||||
const currentUser = platform.auth.getCurrentUser()
|
||||
if (!currentUser) return resolve([])
|
||||
|
||||
const results: GetMyTeamsQuery["myTeams"] = []
|
||||
|
||||
const result = await runGQLQuery({
|
||||
query: GetMyTeamsDocument,
|
||||
variables: {
|
||||
cursor:
|
||||
results.length > 0 ? results[results.length - 1].id : undefined,
|
||||
},
|
||||
})
|
||||
|
||||
if (E.isRight(result)) results.push(...result.right.myTeams)
|
||||
resolve(results)
|
||||
})
|
||||
}
|
||||
|
||||
createSearchSession(
|
||||
query: Readonly<Ref<string>>
|
||||
): [Ref<SpotlightSearcherSessionState>, () => void] {
|
||||
const loading = ref(false)
|
||||
const results = ref<SpotlightSearcherResult[]>([])
|
||||
|
||||
const minisearch = new MiniSearch({
|
||||
fields: ["name", "alternates"],
|
||||
storeFields: ["name"],
|
||||
})
|
||||
|
||||
this.fetchMyTeams().then((teams) => {
|
||||
minisearch.addAll(
|
||||
teams.map((entry) => {
|
||||
return {
|
||||
id: `workspace-${entry.id}`,
|
||||
name: entry.name,
|
||||
alternates: ["team", "workspace", "change", "switch"],
|
||||
}
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
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(IconUsers),
|
||||
score: x.score,
|
||||
text: {
|
||||
type: "text",
|
||||
text: [this.t("workspace.change"), x.name],
|
||||
},
|
||||
}
|
||||
})
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
})
|
||||
|
||||
const onSessionEnd = () => {
|
||||
scopeHandle.stop()
|
||||
minisearch.removeAll()
|
||||
}
|
||||
|
||||
const resultObj = computed<SpotlightSearcherSessionState>(() => ({
|
||||
loading: loading.value,
|
||||
results: results.value,
|
||||
}))
|
||||
|
||||
return [resultObj, onSessionEnd]
|
||||
}
|
||||
|
||||
onResultSelect(result: SpotlightSearcherResult): void {
|
||||
invokeAction("workspace.switch", {
|
||||
teamId: result.id.split("-")[1],
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user