refactor: monorepo+pnpm (removed husky)

This commit is contained in:
Andrew Bastin
2021-09-10 00:28:28 +05:30
parent 917550ff4d
commit b28f82a881
445 changed files with 81301 additions and 63752 deletions

View File

@@ -0,0 +1,26 @@
<template>
<div class="bg-error flex justify-between">
<span
class="
flex
py-2
px-4
transition
relative
items-center
justify-center
group
"
>
<i class="mr-2 material-icons">info_outline</i>
<span class="text-secondaryDark">
<span class="md:hidden">
{{ $t("helpers.offline_short") }}
</span>
<span class="hidden md:inline">
{{ $t("helpers.offline") }}
</span>
</span>
</span>
</div>
</template>

View File

@@ -0,0 +1,185 @@
<template>
<div>
<div class="flex justify-between">
<div class="flex">
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="LEFT_SIDEBAR ? $t('hide.sidebar') : $t('show.sidebar')"
svg="sidebar"
:class="{ 'transform -rotate-180': !LEFT_SIDEBAR }"
@click.native="LEFT_SIDEBAR = !LEFT_SIDEBAR"
/>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="`${
ZEN_MODE ? $t('action.turn_off') : $t('action.turn_on')
} ${$t('layout.zen_mode')}`"
:svg="ZEN_MODE ? 'minimize' : 'maximize'"
:class="{
'!text-accent !focus-visible:text-accentDark !hover:text-accentDark':
ZEN_MODE,
}"
@click.native="ZEN_MODE = !ZEN_MODE"
/>
</div>
<div class="flex">
<span>
<tippy
ref="options"
interactive
trigger="click"
theme="popover"
arrow
>
<template #trigger>
<ButtonSecondary
svg="help-circle"
class="!rounded-none"
:label="$t('app.help')"
/>
</template>
<div class="flex flex-col">
<SmartItem
svg="book"
:label="$t('app.documentation')"
to="https://docs.hoppscotch.io"
blank
@click.native="$refs.options.tippy().hide()"
/>
<SmartItem
svg="zap"
:label="$t('app.keyboard_shortcuts')"
@click.native="
showShortcuts = true
$refs.options.tippy().hide()
"
/>
<SmartItem
svg="gift"
:label="$t('app.whats_new')"
to="https://docs.hoppscotch.io/changelog"
blank
@click.native="$refs.options.tippy().hide()"
/>
<SmartItem
svg="message-circle"
:label="$t('app.chat_with_us')"
@click.native="
chatWithUs()
$refs.options.tippy().hide()
"
/>
<hr />
<SmartItem
svg="twitter"
:label="$t('app.twitter')"
to="https://hoppscotch.io/twitter"
blank
@click.native="$refs.options.tippy().hide()"
/>
<SmartItem
svg="user-plus"
:label="$t('app.invite')"
@click.native="
showShare = true
$refs.options.tippy().hide()
"
/>
<SmartItem
svg="lock"
:label="$t('app.terms_and_privacy')"
to="https://docs.hoppscotch.io/privacy"
blank
@click.native="$refs.options.tippy().hide()"
/>
<!-- <SmartItem :label="$t('app.status')" /> -->
<div class="flex opacity-50 py-2 px-4">
{{ `${$t("app.name")} ${$t("app.version")}` }}
</div>
</div>
</tippy>
</span>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
svg="zap"
:title="$t('app.shortcuts')"
@click.native="showShortcuts = true"
/>
<ButtonSecondary
v-if="navigatorShare"
v-tippy="{ theme: 'tooltip' }"
svg="share-2"
:title="$t('request.share')"
@click.native="nativeShare()"
/>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="RIGHT_SIDEBAR ? $t('hide.sidebar') : $t('show.sidebar')"
svg="sidebar"
class="transform rotate-180"
:class="{ 'rotate-360': !RIGHT_SIDEBAR }"
@click.native="RIGHT_SIDEBAR = !RIGHT_SIDEBAR"
/>
</div>
</div>
<AppShortcuts :show="showShortcuts" @close="showShortcuts = false" />
<AppShare :show="showShare" @hide-modal="showShare = false" />
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from "@nuxtjs/composition-api"
import { defineActionHandler } from "~/helpers/actions"
import { showChat } from "~/helpers/support"
import { useSetting } from "~/newstore/settings"
export default defineComponent({
setup() {
const showShortcuts = ref(false)
const showShare = ref(false)
defineActionHandler("flyouts.keybinds.toggle", () => {
showShortcuts.value = !showShortcuts.value
})
defineActionHandler("modals.share.toggle", () => {
showShare.value = !showShare.value
})
return {
LEFT_SIDEBAR: useSetting("LEFT_SIDEBAR"),
RIGHT_SIDEBAR: useSetting("RIGHT_SIDEBAR"),
ZEN_MODE: useSetting("ZEN_MODE"),
navigatorShare: !!navigator.share,
showShortcuts,
showShare,
}
},
watch: {
ZEN_MODE() {
this.LEFT_SIDEBAR = !this.ZEN_MODE
},
},
methods: {
nativeShare() {
if (navigator.share) {
navigator
.share({
title: "Hoppscotch",
text: "Hoppscotch • Open source API development ecosystem - Helps you create requests faster, saving precious time on development.",
url: "https://hoppscotch.io",
})
.then(() => {})
.catch(console.error)
} else {
// fallback
}
},
chatWithUs() {
showChat()
},
},
})
</script>

View File

@@ -0,0 +1,38 @@
<template>
<div key="outputHash">
<AppPowerSearchEntry
v-for="(shortcut, shortcutIndex) in searchResults"
:key="`shortcut-${shortcutIndex}`"
:ref="`item-${shortcutIndex}`"
:shortcut="shortcut.item"
@action="$emit('action', shortcut.item.action)"
/>
<div
v-if="searchResults.length === 0"
class="flex flex-col text-secondaryLight p-4 items-center justify-center"
>
<i class="opacity-75 pb-2 material-icons">manage_search</i>
<span class="text-center">
{{ $t("state.nothing_found") }} "{{ search }}"
</span>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from "@nuxtjs/composition-api"
import Fuse from "fuse.js"
const props = defineProps<{
input: Record<string, any>[]
search: string
}>()
const options = {
keys: ["keys", "label", "action", "tags"],
}
const fuse = new Fuse(props.input, options)
const searchResults = computed(() => fuse.search(props.search))
</script>

View File

@@ -0,0 +1,36 @@
<template>
<transition name="fade" appear>
<GithubButton
title="Star Hoppscotch"
href="https://github.com/hoppscotch/hoppscotch"
:data-color-scheme="
$colorMode.value != 'light'
? $colorMode.value == 'black'
? 'dark'
: 'dark_dimmed'
: 'light'
"
:data-show-count="true"
data-text="Star"
aria-label="Star Hoppscotch on GitHub"
:data-size="size"
/>
</transition>
</template>
<script>
import { defineComponent } from "@nuxtjs/composition-api"
import GithubButton from "vue-github-button"
export default defineComponent({
components: {
GithubButton,
},
props: {
size: {
type: String,
default: undefined,
},
},
})
</script>

View File

@@ -0,0 +1,166 @@
<template>
<div>
<header
class="flex space-x-2 flex-1 py-2 px-2 items-center justify-between"
>
<div class="space-x-2 inline-flex items-center">
<ButtonSecondary
class="tracking-wide !font-bold !text-secondaryDark"
label="HOPPSCOTCH"
to="/"
/>
<AppGitHubStarButton class="mt-1.5 transition hidden sm:flex" />
</div>
<div class="space-x-2 inline-flex items-center">
<ButtonSecondary
id="installPWA"
v-tippy="{ theme: 'tooltip' }"
:title="$t('header.install_pwa')"
svg="download"
class="rounded"
@click.native="showInstallPrompt()"
/>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="`${$t('app.search')} <kbd>/</kbd>`"
svg="search"
class="rounded"
@click.native="showSearch = true"
/>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="`${$t('support.title')} <kbd>?</kbd>`"
svg="life-buoy"
class="rounded"
@click.native="showSupport = true"
/>
<ButtonSecondary
v-if="currentUser === null"
svg="upload-cloud"
:label="$t('header.save_workspace')"
filled
class="hidden !font-semibold md:flex"
@click.native="showLogin = true"
/>
<ButtonPrimary
v-if="currentUser === null"
:label="$t('header.login')"
@click.native="showLogin = true"
/>
<span v-else class="px-2">
<tippy ref="user" interactive trigger="click" theme="popover" arrow>
<template #trigger>
<ProfilePicture
v-if="currentUser.photoURL"
v-tippy="{
theme: 'tooltip',
}"
:url="currentUser.photoURL"
:alt="currentUser.displayName"
:title="currentUser.displayName"
indicator
:indicator-styles="isOnLine ? 'bg-green-500' : 'bg-red-500'"
/>
<ButtonSecondary
v-else
v-tippy="{ theme: 'tooltip' }"
:title="$t('header.account')"
class="rounded"
svg="user"
/>
</template>
<SmartItem
to="/settings"
svg="settings"
:label="$t('navigation.settings')"
@click.native="$refs.user.tippy().hide()"
/>
<FirebaseLogout @confirm-logout="$refs.user.tippy().hide()" />
</tippy>
</span>
</div>
</header>
<AppAnnouncement v-if="!isOnLine" />
<FirebaseLogin :show="showLogin" @hide-modal="showLogin = false" />
<AppSupport :show="showSupport" @hide-modal="showSupport = false" />
<AppPowerSearch :show="showSearch" @hide-modal="showSearch = false" />
</div>
</template>
<script>
import { defineComponent, ref } from "@nuxtjs/composition-api"
import intializePwa from "~/helpers/pwa"
import { currentUser$ } from "~/helpers/fb/auth"
import { getLocalConfig, setLocalConfig } from "~/newstore/localpersistence"
import { useReadonlyStream } from "~/helpers/utils/composables"
import { defineActionHandler } from "~/helpers/actions"
export default defineComponent({
setup() {
const showSupport = ref(false)
const showSearch = ref(false)
defineActionHandler("modals.support.toggle", () => {
showSupport.value = !showSupport.value
})
defineActionHandler("modals.search.toggle", () => {
showSearch.value = !showSearch.value
})
return {
currentUser: useReadonlyStream(currentUser$, null),
showSupport,
showSearch,
}
},
data() {
return {
// Once the PWA code is initialized, this holds a method
// that can be called to show the user the installation
// prompt.
showInstallPrompt: null,
showLogin: false,
isOnLine: navigator.onLine,
}
},
async mounted() {
window.addEventListener("online", () => {
this.isOnLine = true
})
window.addEventListener("offline", () => {
this.isOnLine = false
})
// Initializes the PWA code - checks if the app is installed,
// etc.
this.showInstallPrompt = await intializePwa()
const cookiesAllowed = getLocalConfig("cookiesAllowed") === "yes"
if (!cookiesAllowed) {
this.$toast.show(this.$t("app.we_use_cookies").toString(), {
icon: "cookie",
duration: 0,
action: [
{
text: this.$t("action.learn_more").toString(),
onClick: (_, toastObject) => {
setLocalConfig("cookiesAllowed", "yes")
toastObject.goAway(0)
window
.open("https://docs.hoppscotch.io/privacy", "_blank")
.focus()
},
},
{
text: this.$t("action.dismiss").toString(),
onClick: (_, toastObject) => {
setLocalConfig("cookiesAllowed", "yes")
toastObject.goAway(0)
},
},
],
})
}
},
})
</script>

View File

@@ -0,0 +1,33 @@
<template>
<svg
class="logo"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
aria-hidden="true"
role="img"
width="24"
height="24"
preserveAspectRatio="xMidYMid meet"
viewBox="0 0 24 24"
>
<path
d="M17 10.54C16.78 7.44 14.63 5 12 5s-4.78 2.44-5 5.54C4 11.23 2 12.5 2 14c0 2.21 4.5 4 10 4s10-1.79 10-4c0-1.5-2-2.77-5-3.46m-2.07 1.3c-1.9.21-3.96.21-5.86 0c-.04-.28-.07-.56-.07-.84c0-2.2 1.35-4 3-4s3 1.8 3 4c0 .28 0 .56-.07.84z"
fill="currentColor"
/>
</svg>
</template>
<style scoped lang="scss">
.logo {
animation: 200ms appear;
}
@keyframes appear {
0% {
@apply opacity-0;
}
100% {
@apply opacity-100;
}
}
</style>

View File

@@ -0,0 +1,77 @@
<template>
<SmartModal v-if="show" full-width @close="$emit('hide-modal')">
<template #body>
<input
id="command"
v-model="search"
v-focus
type="text"
autocomplete="off"
name="command"
:placeholder="$t('app.type_a_command_search').toString()"
class="
bg-transparent
border-b border-dividerLight
flex flex-shrink-0
text-secondaryDark text-base
p-6
"
/>
<AppFuse
v-if="search"
:input="fuse"
:search="search"
@action="runAction"
/>
<div
v-else
class="
divide-y divide-dividerLight
flex flex-col
space-y-4
flex-1
overflow-auto
hide-scrollbar
"
>
<div v-for="(map, mapIndex) in mappings" :key="`map-${mapIndex}`">
<h5 class="my-2 text-secondaryLight py-2 px-6">
{{ $t(map.section) }}
</h5>
<AppPowerSearchEntry
v-for="(shortcut, shortcutIndex) in map.shortcuts"
:key="`map-${mapIndex}-shortcut-${shortcutIndex}`"
:shortcut="shortcut"
@action="runAction"
/>
</div>
</div>
</template>
</SmartModal>
</template>
<script setup lang="ts">
import { ref } from "@nuxtjs/composition-api"
import { HoppAction, invokeAction } from "~/helpers/actions"
import { spotlight as mappings, fuse } from "~/helpers/shortcuts"
defineProps<{
show: boolean
}>()
const emit = defineEmits<{
(e: "hide-modal"): void
}>()
const search = ref("")
const hideModal = () => {
search.value = ""
emit("hide-modal")
}
const runAction = (command: HoppAction) => {
invokeAction(command)
hideModal()
}
</script>

View File

@@ -0,0 +1,66 @@
<template>
<div
class="
cursor-pointer
flex
py-2
px-6
transition
items-center
group
hover:bg-primaryLight
focus:outline-none
focus-visible:bg-primaryLight
"
tabindex="0"
@click="$emit('action', shortcut.action)"
@keydown.enter="$emit('action', shortcut.action)"
>
<SmartIcon
class="
mr-4
opacity-75
transition
svg-icons
group-hover:opacity-100
group-focus:opacity-100
"
:name="shortcut.icon"
/>
<span
class="
flex flex-1
mr-4
transition
group-hover:text-secondaryDark
group-focus:text-secondaryDark
"
>
{{ $t(shortcut.label) }}
</span>
<span
v-for="(key, keyIndex) in shortcut.keys"
:key="`key-${keyIndex}`"
class="shortcut-key"
>
{{ key }}
</span>
</div>
</template>
<script setup lang="ts">
defineProps<{
shortcut: Object
}>()
</script>
<style lang="scss" scoped>
.shortcut-key {
@apply bg-dividerLight;
@apply rounded;
@apply ml-2;
@apply py-1;
@apply px-2;
@apply inline-flex;
}
</style>

View File

@@ -0,0 +1,18 @@
<template>
<section :id="label.toLowerCase()" class="flex flex-col flex-1 relative">
<slot></slot>
</section>
</template>
<script lang="ts">
import { defineComponent } from "@nuxtjs/composition-api"
export default defineComponent({
props: {
label: {
type: String,
default: "Section",
},
},
})
</script>

View File

@@ -0,0 +1,125 @@
<template>
<SmartModal
v-if="show"
:title="$t('app.invite_your_friends')"
@close="$emit('hide-modal')"
>
<template #body>
<p class="text-secondaryLight mb-8 px-2">
{{ $t("app.invite_description") }}
</p>
<div class="flex flex-col space-y-2 px-2">
<div class="grid gap-4 grid-cols-3">
<a
v-for="(platform, index) in platforms"
:key="`platform-${index}`"
:href="platform.link"
target="_blank"
class="share-link"
>
<SmartIcon :name="platform.icon" class="h-6 w-6" />
<span class="mt-3">
{{ platform.name }}
</span>
</a>
<button class="share-link" @click="copyAppLink">
<SmartIcon class="h-6 text-xl w-6" :name="copyIcon" />
<span class="mt-3">
{{ $t("app.copy") }}
</span>
</button>
</div>
</div>
</template>
</SmartModal>
</template>
<script>
import { defineComponent } from "@nuxtjs/composition-api"
import { copyToClipboard } from "~/helpers/utils/clipboard"
export default defineComponent({
props: {
show: Boolean,
},
data() {
const url = "https://hoppscotch.io"
const text = "Hoppscotch - Open source API development ecosystem."
const description =
"Helps you create requests faster, saving precious time on development."
const subject =
"Checkout Hoppscotch - an open source API development ecosystem"
const summary = `Hi there!%0D%0A%0D%0AI thought youll like this new platform that I joined called Hoppscotch - https://hoppscotch.io.%0D%0AIt is a simple and intuitive interface for creating and managing your APIs. You can build, test, document, and share your APIs.%0D%0A%0D%0AThe best part about Hoppscotch is that it is open source and free to get started.%0D%0A%0D%0A`
const twitter = "hoppscotch_io"
return {
url: "https://hoppscotch.io",
copyIcon: "copy",
platforms: [
{
name: "Email",
icon: "mail",
link: `mailto:?subject=${subject}&body=${summary}`,
},
{
name: "Twitter",
icon: "brands/twitter",
link: `https://twitter.com/intent/tweet?text=${text} ${description}&url=${url}&via=${twitter}`,
},
{
name: "Facebook",
icon: "brands/facebook",
link: `https://www.facebook.com/sharer/sharer.php?u=${url}`,
},
{
name: "Reddit",
icon: "brands/reddit",
link: `https://www.reddit.com/submit?url=${url}&title=${text}`,
},
{
name: "LinkedIn",
icon: "brands/linkedin",
link: `https://www.linkedin.com/sharing/share-offsite/?url=${url}`,
},
],
}
},
methods: {
copyAppLink() {
copyToClipboard(this.url)
this.copyIcon = "check"
this.$toast.success(this.$t("state.copied_to_clipboard").toString(), {
icon: "content_paste",
})
setTimeout(() => (this.copyIcon = "copy"), 1000)
},
hideModal() {
this.$emit("hide-modal")
},
},
})
</script>
<style lang="scss" scoped>
.share-link {
@apply border border-dividerLight;
@apply rounded;
@apply flex-col flex;
@apply p-4;
@apply items-center;
@apply justify-center;
@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>

View File

@@ -0,0 +1,140 @@
<template>
<AppSlideOver :show="show" @close="close()">
<template #content>
<div
class="
bg-primary
border-b border-dividerLight
flex
p-2
top-0
z-10
items-center
sticky
justify-between
"
>
<h3 class="ml-4 heading">{{ $t("app.shortcuts") }}</h3>
<div class="flex">
<ButtonSecondary svg="x" class="rounded" @click.native="close()" />
</div>
</div>
<div class="bg-primary border-b border-dividerLight">
<div class="flex flex-col my-4 mx-6">
<input
v-model="filterText"
v-focus
type="search"
autocomplete="off"
class="
bg-primaryLight
border border-dividerLight
rounded
flex
w-full
py-2
px-4
focus-visible:border-divider
"
:placeholder="$t('action.search')"
/>
</div>
</div>
<div v-if="filterText">
<div
v-for="(map, mapIndex) in searchResults"
:key="`map-${mapIndex}`"
class="space-y-4 py-4 px-6"
>
<h1 class="font-semibold text-secondaryDark">
{{ $t(map.item.section) }}
</h1>
<AppShortcutsEntry
v-for="(shortcut, index) in map.item.shortcuts"
:key="`shortcut-${index}`"
:shortcut="shortcut"
/>
</div>
<div
v-if="searchResults.length === 0"
class="
flex flex-col
text-secondaryLight
p-4
items-center
justify-center
"
>
<i class="opacity-75 pb-2 material-icons">manage_search</i>
<span class="text-center">
{{ $t("state.nothing_found") }} "{{ filterText }}"
</span>
</div>
</div>
<div
v-else
class="
divide-y divide-dividerLight
flex flex-col flex-1
overflow-auto
hide-scrollbar
"
>
<div
v-for="(map, mapIndex) in mappings"
:key="`map-${mapIndex}`"
class="space-y-4 py-4 px-6"
>
<h1 class="font-semibold text-secondaryDark">
{{ $t(map.section) }}
</h1>
<AppShortcutsEntry
v-for="(shortcut, shortcutIndex) in map.shortcuts"
:key="`map-${mapIndex}-shortcut-${shortcutIndex}`"
:shortcut="shortcut"
/>
</div>
</div>
</template>
</AppSlideOver>
</template>
<script setup lang="ts">
import { computed, ref } from "@nuxtjs/composition-api"
import Fuse from "fuse.js"
import mappings from "~/helpers/shortcuts"
defineProps<{
show: boolean
}>()
const options = {
keys: ["shortcuts.label"],
}
const fuse = new Fuse(mappings, options)
const filterText = ref("")
const searchResults = computed(() => fuse.search(filterText.value))
const emit = defineEmits<{
(e: "close"): void
}>()
const close = () => {
filterText.value = ""
emit("close")
}
</script>
<style lang="scss" scoped>
.shortcut-key {
@apply bg-dividerLight;
@apply rounded;
@apply ml-2;
@apply py-1;
@apply px-2;
@apply inline-flex;
}
</style>

View File

@@ -0,0 +1,31 @@
<template>
<div class="flex items-center">
<span class="flex flex-1 mr-4">
{{ $t(shortcut.label) }}
</span>
<span
v-for="(key, index) in shortcut.keys"
:key="`key-${index}`"
class="shortcut-key"
>
{{ key }}
</span>
</div>
</template>
<script setup lang="ts">
defineProps<{
shortcut: Object
}>()
</script>
<style lang="scss" scoped>
.shortcut-key {
@apply bg-dividerLight;
@apply rounded;
@apply ml-2;
@apply py-1;
@apply px-2;
@apply inline-flex;
}
</style>

View File

@@ -0,0 +1,133 @@
<template>
<aside class="flex h-full justify-between md:flex-col">
<nav class="flex flex-nowrap md:flex-col">
<NuxtLink
v-for="(navigation, index) in primaryNavigation"
:key="`navigation-${index}`"
:to="localePath(navigation.target)"
class="nav-link"
tabindex="0"
>
<i v-if="navigation.icon" class="material-icons">
{{ navigation.icon }}
</i>
<div v-if="navigation.svg">
<SmartIcon :name="navigation.svg" class="svg-icons" />
</div>
<span v-if="LEFT_SIDEBAR">{{ navigation.title }}</span>
<tippy
v-if="!LEFT_SIDEBAR"
:placement="windowInnerWidth.x.value >= 768 ? 'right' : 'bottom'"
theme="tooltip"
:content="navigation.title"
/>
</NuxtLink>
</nav>
</aside>
</template>
<script lang="ts">
import { defineComponent } from "@nuxtjs/composition-api"
import useWindowSize from "~/helpers/utils/useWindowSize"
import { useSetting } from "~/newstore/settings"
export default defineComponent({
setup() {
return {
windowInnerWidth: useWindowSize(),
LEFT_SIDEBAR: useSetting("LEFT_SIDEBAR"),
}
},
data() {
return {
primaryNavigation: [
{
target: "index",
svg: "link-2",
title: this.$t("navigation.rest"),
},
{
target: "graphql",
svg: "graphql",
title: this.$t("navigation.graphql"),
},
{
target: "realtime",
svg: "globe",
title: this.$t("navigation.realtime"),
},
{
target: "documentation",
svg: "book-open",
title: this.$t("navigation.doc"),
},
{
target: "settings",
svg: "settings",
title: this.$t("navigation.settings"),
},
],
}
},
})
</script>
<style scoped lang="scss">
.nav-link {
@apply relative;
@apply p-4;
@apply flex flex-col flex-1;
@apply items-center;
@apply justify-center;
@apply hover:(bg-primaryDark text-secondaryDark);
@apply focus-visible:text-secondaryDark;
&::after {
@apply absolute;
@apply inset-x-0;
@apply md:inset-x-auto;
@apply md:inset-y-0;
@apply bottom-0;
@apply md:bottom-auto;
@apply md:left-0;
@apply z-2;
@apply h-0.5;
@apply md:h-full;
@apply w-full;
@apply md:w-0.5;
content: "";
}
&:focus::after {
@apply bg-divider;
}
.material-icons,
.svg-icons {
@apply opacity-75;
}
span {
@apply mt-2;
@apply font-font-medium;
font-size: calc(var(--body-font-size) - 0.062rem);
}
&.exact-active-link {
@apply text-secondaryDark;
@apply bg-primaryLight;
@apply hover:text-secondaryDark;
.material-icons,
.svg-icons {
@apply opacity-100;
}
&::after {
@apply bg-accent;
}
}
}
</style>

View File

@@ -0,0 +1,69 @@
<template>
<div>
<transition v-if="show" name="fade" appear>
<div class="inset-0 transition-opacity z-20 fixed" @keydown.esc="close()">
<div
class="bg-primaryDark opacity-90 inset-0 absolute"
tabindex="0"
@click="close()"
></div>
</div>
</transition>
<aside
class="
bg-primary
flex flex-col
h-full
max-w-full
transform
transition
top-0
ease-in-out
right-0
w-96
z-30
duration-300
fixed
overflow-auto
"
:class="show ? 'shadow-xl translate-x-0' : 'translate-x-full'"
>
<slot name="content"></slot>
</aside>
</div>
</template>
<script>
import { defineComponent } from "@nuxtjs/composition-api"
export default defineComponent({
props: {
show: {
type: Boolean,
required: true,
default: false,
},
},
watch: {
show: {
immediate: true,
handler(show) {
if (process.client) {
if (show) document.body.style.setProperty("overflow", "hidden")
else document.body.style.removeProperty("overflow")
}
},
},
},
mounted() {
document.addEventListener("keydown", (e) => {
if (e.keyCode === 27 && this.show) this.close()
})
},
methods: {
close() {
this.$emit("close")
},
},
})
</script>

View File

@@ -0,0 +1,97 @@
<template>
<SmartModal
v-if="show"
:title="$t('support.title')"
@close="$emit('hide-modal')"
>
<template #body>
<div class="flex flex-col space-y-2">
<SmartItem
svg="book"
:label="$t('app.documentation')"
to="https://docs.hoppscotch.io"
:description="$t('support.documentation')"
info-icon="chevron_right"
active
blank
@click.native="hideModal()"
/>
<SmartItem
svg="zap"
:label="$t('app.keyboard_shortcuts')"
:description="$t('support.shortcuts')"
info-icon="chevron_right"
active
@click.native="
showShortcuts()
hideModal()
"
/>
<SmartItem
svg="gift"
:label="$t('app.whats_new')"
to="https://docs.hoppscotch.io/changelog"
:description="$t('support.changelog')"
info-icon="chevron_right"
active
blank
@click.native="hideModal()"
/>
<SmartItem
svg="message-circle"
:label="$t('app.chat_with_us')"
:description="$t('support.chat')"
info-icon="chevron_right"
active
@click.native="
chatWithUs()
hideModal()
"
/>
<SmartItem
svg="brands/discord"
:label="$t('app.join_discord_community')"
to="https://hoppscotch.io/discord"
blank
:description="$t('support.community')"
info-icon="chevron_right"
active
@click.native="hideModal()"
/>
<SmartItem
svg="brands/twitter"
:label="$t('app.twitter')"
to="https://hoppscotch.io/twitter"
blank
:description="$t('support.twitter')"
info-icon="chevron_right"
active
@click.native="hideModal()"
/>
</div>
</template>
</SmartModal>
</template>
<script>
import { defineComponent } from "@nuxtjs/composition-api"
import { invokeAction } from "~/helpers/actions"
import { showChat } from "~/helpers/support"
export default defineComponent({
props: {
show: Boolean,
},
methods: {
chatWithUs() {
showChat()
},
showShortcuts() {
invokeAction("flyouts.keybinds.toggle")
},
hideModal() {
this.$emit("hide-modal")
},
},
})
</script>