Compare commits
21 Commits
2023.12.1
...
fix/shared
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4aaf5c279c | ||
|
|
dcb8b2c511 | ||
|
|
58d3ef900f | ||
|
|
0a94c169bd | ||
|
|
80eb4c7701 | ||
|
|
6ed4b73a34 | ||
|
|
52ef2d8d32 | ||
|
|
53e013e2a4 | ||
|
|
4a14de76c9 | ||
|
|
a4aa808103 | ||
|
|
6d7b0e11a3 | ||
|
|
ffff54b5af | ||
|
|
b81ccb4ee3 | ||
|
|
27d0a7c437 | ||
|
|
aca96dd5f2 | ||
|
|
c0dbcc901f | ||
|
|
ba52c8cc37 | ||
|
|
d1f6f40ef8 | ||
|
|
99f5070f71 | ||
|
|
cd371fc9d4 | ||
|
|
59fef248c0 |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hoppscotch-backend",
|
||||
"version": "2023.12.1",
|
||||
"version": "2023.12.2",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@hoppscotch/common",
|
||||
"private": true,
|
||||
"version": "2023.12.1-1",
|
||||
"version": "2023.12.2",
|
||||
"scripts": {
|
||||
"dev": "pnpm exec npm-run-all -p -l dev:*",
|
||||
"test": "vitest --run",
|
||||
|
||||
@@ -70,7 +70,11 @@
|
||||
v-model:option-tab="selectedOptionTab"
|
||||
:properties="properties"
|
||||
/>
|
||||
<HttpResponse :document="tab.document" :is-embed="true" />
|
||||
<HttpResponse
|
||||
v-if="tab.document.response"
|
||||
:document="tab.document"
|
||||
:is-embed="true"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -88,18 +92,19 @@ import { runRESTRequest$ } from "~/helpers/RequestRunner"
|
||||
import { HoppTab } from "~/services/tab"
|
||||
import { HoppRESTDocument } from "~/helpers/rest/document"
|
||||
import IconSave from "~icons/lucide/save"
|
||||
import { RESTOptionTabs } from "../http/RequestOptions.vue"
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
|
||||
const props = defineProps<{
|
||||
modelTab: HoppTab<HoppRESTDocument>
|
||||
properties: string[]
|
||||
properties: RESTOptionTabs[]
|
||||
sharedRequestID: string
|
||||
}>()
|
||||
|
||||
const tab = useModel(props, "modelTab")
|
||||
|
||||
const selectedOptionTab = ref(props.properties[0])
|
||||
const selectedOptionTab = ref<RESTOptionTabs>(props.properties[0])
|
||||
|
||||
const requestCancelFunc: Ref<(() => void) | null> = ref(null)
|
||||
|
||||
|
||||
@@ -187,7 +187,7 @@ import IconClock from "~icons/lucide/clock"
|
||||
import IconCopy from "~icons/lucide/copy"
|
||||
import IconBox from "~icons/lucide/box"
|
||||
import { computed, nextTick, reactive, ref } from "vue"
|
||||
import { GraphQLField, GraphQLType } from "graphql"
|
||||
import { GraphQLField, GraphQLType, getNamedType } from "graphql"
|
||||
import { refAutoReset } from "@vueuse/core"
|
||||
import { useCodemirror } from "@composables/codemirror"
|
||||
import { copyToClipboard } from "@helpers/utils/clipboard"
|
||||
@@ -260,12 +260,6 @@ function getFilteredGraphqlTypes(filterText: string, types: GraphQLType[]) {
|
||||
})
|
||||
}
|
||||
|
||||
function resolveRootType(type: GraphQLType) {
|
||||
let t: any = type
|
||||
while (t.ofType) t = t.ofType
|
||||
return t
|
||||
}
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
const downloadSchemaIcon = refAutoReset<typeof IconDownload | typeof IconCheck>(
|
||||
@@ -331,7 +325,7 @@ const handleJumpToType = async (type: GraphQLType) => {
|
||||
selectedGqlTab.value = "types"
|
||||
await nextTick()
|
||||
|
||||
const rootTypeName = resolveRootType(type).name
|
||||
const rootTypeName = getNamedType(type).name
|
||||
const target = document.getElementById(`type_${rootTypeName}`)
|
||||
if (target) {
|
||||
target.scrollIntoView({ block: "center", behavior: "smooth" })
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { GraphQLScalarType, GraphQLType } from "graphql"
|
||||
import { GraphQLScalarType, GraphQLType, getNamedType } from "graphql"
|
||||
import { computed } from "vue"
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -21,15 +21,9 @@ const emit = defineEmits<{
|
||||
|
||||
const typeString = computed(() => `${props.gqlType}`)
|
||||
const isScalar = computed(() => {
|
||||
return resolveRootType(props.gqlType) instanceof GraphQLScalarType
|
||||
return getNamedType(props.gqlType) instanceof GraphQLScalarType
|
||||
})
|
||||
|
||||
function resolveRootType(type: GraphQLType) {
|
||||
let t = type as any
|
||||
while (t.ofType !== null) t = t.ofType
|
||||
return t
|
||||
}
|
||||
|
||||
function jumpToType() {
|
||||
if (isScalar.value) return
|
||||
emit("jump-to-type", props.gqlType)
|
||||
|
||||
@@ -99,6 +99,7 @@ useCodemirror(
|
||||
linter,
|
||||
completer,
|
||||
environmentHighlights: false,
|
||||
contextMenuEnabled: false,
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
@@ -237,7 +237,7 @@ import { useReadonlyStream, useStreamSubscriber } from "@composables/stream"
|
||||
import { useToast } from "@composables/toast"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
import * as E from "fp-ts/Either"
|
||||
import { Ref, computed, onBeforeUnmount, ref } from "vue"
|
||||
import { Ref, computed, ref, onUnmounted } from "vue"
|
||||
import { defineActionHandler, invokeAction } from "~/helpers/actions"
|
||||
import { runMutation } from "~/helpers/backend/GQLClient"
|
||||
import { UpdateRequestDocument } from "~/helpers/backend/graphql"
|
||||
@@ -322,6 +322,10 @@ const userHistories = computed(() => {
|
||||
return history.value.map((history) => history.request.endpoint).slice(0, 10)
|
||||
})
|
||||
|
||||
const inspectionService = useService(InspectionService)
|
||||
|
||||
const tabs = useService(RESTTabService)
|
||||
|
||||
const newSendRequest = async () => {
|
||||
if (newEndpoint.value === "" || /^\s+$/.test(newEndpoint.value)) {
|
||||
toast.error(`${t("empty.endpoint")}`)
|
||||
@@ -422,6 +426,17 @@ function isCURL(curl: string) {
|
||||
return curl.includes("curl ")
|
||||
}
|
||||
|
||||
const currentTabID = tabs.currentTabID.value
|
||||
|
||||
onUnmounted(() => {
|
||||
//check if current tab id exist in the current tab id lists
|
||||
const isCurrentTabRemoved = !tabs
|
||||
.getActiveTabs()
|
||||
.value.some((tab) => tab.id === currentTabID)
|
||||
|
||||
if (isCurrentTabRemoved) cancelRequest()
|
||||
})
|
||||
|
||||
const cancelRequest = () => {
|
||||
loading.value = false
|
||||
requestCancelFunc.value?.()
|
||||
@@ -553,10 +568,6 @@ const saveRequest = () => {
|
||||
|
||||
const request = ref<HoppRESTRequest | null>(null)
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (loading.value) cancelRequest()
|
||||
})
|
||||
|
||||
defineActionHandler("request.send-cancel", () => {
|
||||
if (!loading.value) newSendRequest()
|
||||
else cancelRequest()
|
||||
@@ -607,8 +618,5 @@ const isCustomMethod = computed(() => {
|
||||
|
||||
const COLUMN_LAYOUT = useSetting("COLUMN_LAYOUT")
|
||||
|
||||
const inspectionService = useService(InspectionService)
|
||||
|
||||
const tabs = useService(RESTTabService)
|
||||
const tabResults = inspectionService.getResultViewFor(tabs.currentTabID.value)
|
||||
</script>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
render-inactive-tabs
|
||||
>
|
||||
<HoppSmartTab
|
||||
v-if="properties ? properties.includes('parameters') : true"
|
||||
v-if="properties ? properties.includes('params') : true"
|
||||
:id="'params'"
|
||||
:label="`${t('tab.parameters')}`"
|
||||
:info="`${newActiveParamsCount$}`"
|
||||
@@ -13,7 +13,7 @@
|
||||
<HttpParameters v-model="request.params" />
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab
|
||||
v-if="properties ? properties.includes('body') : true"
|
||||
v-if="properties ? properties.includes('bodyParams') : true"
|
||||
:id="'bodyParams'"
|
||||
:label="`${t('tab.body')}`"
|
||||
>
|
||||
|
||||
@@ -95,6 +95,7 @@ useCodemirror(
|
||||
linter,
|
||||
completer,
|
||||
environmentHighlights: false,
|
||||
contextMenuEnabled: false,
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ const widgets: Widget[] = [
|
||||
},
|
||||
]
|
||||
|
||||
type Tabs = "parameters" | "body" | "headers" | "authorization"
|
||||
type Tabs = "params" | "bodyParams" | "headers" | "authorization"
|
||||
|
||||
type EmbedOption = {
|
||||
selectedTab: Tabs
|
||||
@@ -116,15 +116,15 @@ type EmbedOption = {
|
||||
}
|
||||
|
||||
const embedOption = ref<EmbedOption>({
|
||||
selectedTab: "parameters",
|
||||
selectedTab: "params",
|
||||
tabs: [
|
||||
{
|
||||
value: "parameters",
|
||||
value: "params",
|
||||
label: t("tab.parameters"),
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
value: "body",
|
||||
value: "bodyParams",
|
||||
label: t("tab.body"),
|
||||
enabled: true,
|
||||
},
|
||||
|
||||
@@ -225,10 +225,10 @@ const props = defineProps({
|
||||
embedOptions: {
|
||||
type: Object as PropType<EmbedOption>,
|
||||
default: () => ({
|
||||
selectedTab: "parameters",
|
||||
selectedTab: "params",
|
||||
tabs: [
|
||||
{
|
||||
value: "parameters",
|
||||
value: "params",
|
||||
label: "shared_requests.parameters",
|
||||
enabled: true,
|
||||
},
|
||||
@@ -290,7 +290,7 @@ const widgets: Widget[] = [
|
||||
},
|
||||
]
|
||||
|
||||
type EmbedTabs = "parameters" | "body" | "headers" | "authorization"
|
||||
type EmbedTabs = "params" | "bodyParams" | "headers" | "authorization"
|
||||
|
||||
type EmbedOption = {
|
||||
selectedTab: EmbedTabs
|
||||
|
||||
@@ -56,7 +56,7 @@ import { useI18n } from "~/composables/i18n"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
type EmbedTabs = "parameters" | "body" | "headers" | "authorization"
|
||||
type EmbedTabs = "params" | "bodyParams" | "headers" | "authorization"
|
||||
|
||||
type EmbedOption = {
|
||||
selectedTab: EmbedTabs
|
||||
@@ -93,15 +93,15 @@ const props = defineProps({
|
||||
embedOptions: {
|
||||
type: Object as PropType<EmbedOption>,
|
||||
default: () => ({
|
||||
selectedTab: "parameters",
|
||||
selectedTab: "params",
|
||||
tabs: [
|
||||
{
|
||||
value: "parameters",
|
||||
value: "params",
|
||||
label: "shared_requests.parameters",
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
value: "body",
|
||||
value: "bodyParams",
|
||||
label: "shared_requests.body",
|
||||
enabled: true,
|
||||
},
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<div v-if="loading" class="flex flex-col items-center justify-center">
|
||||
<div v-if="loading" class="flex flex-col items-center justify-center p-4">
|
||||
<HoppSmartSpinner class="mb-4" />
|
||||
<span class="text-secondaryLight">{{ t("state.loading") }}</span>
|
||||
</div>
|
||||
@@ -136,15 +136,15 @@ const shareRequestCreatingLoading = ref(false)
|
||||
const requestToShare = ref<HoppRESTRequest | null>(null)
|
||||
|
||||
const embedOptions = ref<EmbedOption>({
|
||||
selectedTab: "parameters",
|
||||
selectedTab: "params",
|
||||
tabs: [
|
||||
{
|
||||
value: "parameters",
|
||||
value: "params",
|
||||
label: t("tab.parameters"),
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
value: "body",
|
||||
value: "bodyParams",
|
||||
label: t("tab.body"),
|
||||
enabled: false,
|
||||
},
|
||||
@@ -208,7 +208,7 @@ const currentUser = useReadonlyStream(
|
||||
|
||||
const step = ref(1)
|
||||
|
||||
type EmbedTabs = "parameters" | "body" | "headers" | "authorization"
|
||||
type EmbedTabs = "params" | "bodyParams" | "headers" | "authorization"
|
||||
|
||||
type EmbedOption = {
|
||||
selectedTab: EmbedTabs
|
||||
@@ -249,7 +249,15 @@ const loading = computed(
|
||||
|
||||
onLoggedIn(() => {
|
||||
try {
|
||||
adapter.initialize()
|
||||
// wait for a bit to let the auth token to be set
|
||||
// because in some race conditions, the token is not set this fixes that
|
||||
const initLoadTimeout = setTimeout(() => {
|
||||
adapter.initialize()
|
||||
}, 10)
|
||||
|
||||
return () => {
|
||||
clearTimeout(initLoadTimeout)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
@@ -313,15 +321,15 @@ const displayCustomizeRequestModal = (
|
||||
info: t("shared_requests.button_info"),
|
||||
}
|
||||
embedOptions.value = {
|
||||
selectedTab: "parameters",
|
||||
selectedTab: "params",
|
||||
tabs: [
|
||||
{
|
||||
value: "parameters",
|
||||
value: "params",
|
||||
label: t("tab.parameters"),
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
value: "body",
|
||||
value: "bodyParams",
|
||||
label: t("tab.body"),
|
||||
enabled: false,
|
||||
},
|
||||
@@ -451,7 +459,7 @@ const getErrorMessage = (err: GQLError<string>) => {
|
||||
}
|
||||
switch (err.error) {
|
||||
case "shortcode/not_found":
|
||||
return t("shared_request.not_found")
|
||||
return t("shared_requests.not_found")
|
||||
default:
|
||||
return t("error.something_went_wrong")
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ import { computed } from "vue"
|
||||
|
||||
import { useI18n } from "~/composables/i18n"
|
||||
|
||||
type Tabs = "parameters" | "body" | "headers" | "authorization"
|
||||
type Tabs = "params" | "bodyParams" | "headers" | "authorization"
|
||||
|
||||
type EmbedOption = {
|
||||
selectedTab: Tabs
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
v-if="currentSuggestionIndex === index"
|
||||
class="hidden items-center text-secondary md:flex"
|
||||
>
|
||||
<kbd class="shortcut-key">TAB</kbd>
|
||||
<kbd class="shortcut-key">Enter</kbd>
|
||||
<span class="ml-2 truncate">to select</span>
|
||||
</div>
|
||||
</li>
|
||||
@@ -79,6 +79,7 @@ const props = withDefaults(
|
||||
readonly?: boolean
|
||||
autoCompleteSource?: string[]
|
||||
inspectionResults?: InspectorResult[] | undefined
|
||||
contextMenuEnabled?: boolean
|
||||
}>(),
|
||||
{
|
||||
modelValue: "",
|
||||
@@ -91,6 +92,7 @@ const props = withDefaults(
|
||||
autoCompleteSource: undefined,
|
||||
inspectionResult: undefined,
|
||||
inspectionResults: undefined,
|
||||
contextMenuEnabled: true,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -167,36 +169,41 @@ watch(
|
||||
)
|
||||
|
||||
const handleKeystroke = (ev: KeyboardEvent) => {
|
||||
if (["ArrowDown", "ArrowUp", "Enter", "Tab", "Escape"].includes(ev.key)) {
|
||||
if (!props.autoCompleteSource) return
|
||||
|
||||
if (["ArrowDown", "ArrowUp", "Enter", "Escape"].includes(ev.key)) {
|
||||
ev.preventDefault()
|
||||
}
|
||||
|
||||
if (ev.shiftKey) {
|
||||
if (["Escape", "Tab", "Shift"].includes(ev.key)) {
|
||||
showSuggestionPopover.value = false
|
||||
return
|
||||
}
|
||||
|
||||
showSuggestionPopover.value = true
|
||||
if (ev.key === "Enter") {
|
||||
if (suggestions.value.length > 0 && currentSuggestionIndex.value > -1) {
|
||||
updateModelValue(suggestions.value[currentSuggestionIndex.value])
|
||||
currentSuggestionIndex.value = -1
|
||||
|
||||
if (
|
||||
["Enter", "Tab"].includes(ev.key) &&
|
||||
suggestions.value.length > 0 &&
|
||||
currentSuggestionIndex.value > -1
|
||||
) {
|
||||
updateModelValue(suggestions.value[currentSuggestionIndex.value])
|
||||
currentSuggestionIndex.value = -1
|
||||
|
||||
//used to set codemirror cursor at the end of the line after selecting a suggestion
|
||||
nextTick(() => {
|
||||
view.value?.dispatch({
|
||||
selection: EditorSelection.create([
|
||||
EditorSelection.range(
|
||||
props.modelValue.length,
|
||||
props.modelValue.length
|
||||
),
|
||||
]),
|
||||
//used to set codemirror cursor at the end of the line after selecting a suggestion
|
||||
nextTick(() => {
|
||||
view.value?.dispatch({
|
||||
selection: EditorSelection.create([
|
||||
EditorSelection.range(
|
||||
props.modelValue.length,
|
||||
props.modelValue.length
|
||||
),
|
||||
]),
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
if (showSuggestionPopover.value) {
|
||||
showSuggestionPopover.value = false
|
||||
} else {
|
||||
emit("enter", ev)
|
||||
}
|
||||
} else {
|
||||
showSuggestionPopover.value = true
|
||||
}
|
||||
|
||||
if (ev.key === "ArrowDown") {
|
||||
@@ -221,15 +228,6 @@ const handleKeystroke = (ev: KeyboardEvent) => {
|
||||
emit("keyup", ev)
|
||||
}
|
||||
|
||||
if (ev.key === "Enter") {
|
||||
emit("enter", ev)
|
||||
showSuggestionPopover.value = false
|
||||
}
|
||||
|
||||
if (ev.key === "Escape") {
|
||||
showSuggestionPopover.value = false
|
||||
}
|
||||
|
||||
// used to scroll to the first suggestion when left arrow is pressed
|
||||
if (ev.key === "ArrowLeft") {
|
||||
if (suggestions.value.length > 0) {
|
||||
@@ -359,8 +357,11 @@ const initView = (el: any) => {
|
||||
handleTextSelection()
|
||||
}, 140)
|
||||
|
||||
el.addEventListener("mouseup", debounceFn)
|
||||
el.addEventListener("keyup", debounceFn)
|
||||
// Only add event listeners if context menu is enabled in the component
|
||||
if (props.contextMenuEnabled) {
|
||||
el.addEventListener("mouseup", debounceFn)
|
||||
el.addEventListener("keyup", debounceFn)
|
||||
}
|
||||
|
||||
const extensions: Extension = [
|
||||
EditorView.contentAttributes.of({ "aria-label": props.placeholder }),
|
||||
@@ -396,7 +397,7 @@ const initView = (el: any) => {
|
||||
ev.preventDefault()
|
||||
},
|
||||
scroll(event) {
|
||||
if (event.target) {
|
||||
if (event.target && props.contextMenuEnabled) {
|
||||
handleTextSelection()
|
||||
}
|
||||
},
|
||||
@@ -405,7 +406,6 @@ const initView = (el: any) => {
|
||||
class {
|
||||
update(update: ViewUpdate) {
|
||||
if (props.readonly) return
|
||||
|
||||
if (update.docChanged) {
|
||||
const prevValue = clone(cachedValue.value)
|
||||
|
||||
@@ -436,6 +436,17 @@ const initView = (el: any) => {
|
||||
clipboardEv = null
|
||||
pastedValue = null
|
||||
}
|
||||
|
||||
if (props.contextMenuEnabled) {
|
||||
// close the context menu if text is being updated in the editor
|
||||
invokeAction("contextmenu.open", {
|
||||
position: {
|
||||
top: 0,
|
||||
left: 0,
|
||||
},
|
||||
text: null,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,8 @@ type CodeMirrorOptions = {
|
||||
|
||||
additionalExts?: Extension[]
|
||||
|
||||
contextMenuEnabled?: boolean
|
||||
|
||||
// callback on editor update
|
||||
onUpdate?: (view: ViewUpdate) => void
|
||||
}
|
||||
@@ -208,6 +210,9 @@ export function useCodemirror(
|
||||
): { cursor: Ref<{ line: number; ch: number }> } {
|
||||
const { subscribeToStream } = useStreamSubscriber()
|
||||
|
||||
// Set default value for contextMenuEnabled if not provided
|
||||
options.contextMenuEnabled = options.contextMenuEnabled ?? true
|
||||
|
||||
const additionalExts = new Compartment()
|
||||
const language = new Compartment()
|
||||
const lineWrapping = new Compartment()
|
||||
@@ -272,8 +277,11 @@ export function useCodemirror(
|
||||
handleTextSelection()
|
||||
}, 140)
|
||||
|
||||
el.addEventListener("mouseup", debounceFn)
|
||||
el.addEventListener("keyup", debounceFn)
|
||||
// Only add event listeners if context menu is enabled in the editor
|
||||
if (options.contextMenuEnabled) {
|
||||
el.addEventListener("mouseup", debounceFn)
|
||||
el.addEventListener("keyup", debounceFn)
|
||||
}
|
||||
|
||||
if (options.onUpdate) {
|
||||
options.onUpdate(update)
|
||||
@@ -312,7 +320,7 @@ export function useCodemirror(
|
||||
),
|
||||
EditorView.domEventHandlers({
|
||||
scroll(event) {
|
||||
if (event.target) {
|
||||
if (event.target && options.contextMenuEnabled) {
|
||||
handleTextSelection()
|
||||
}
|
||||
},
|
||||
|
||||
@@ -154,6 +154,9 @@ export function runRESTRequest$(
|
||||
)
|
||||
|
||||
if (E.isRight(runResult)) {
|
||||
// set the response in the tab so that multiple tabs can run request simultaneously
|
||||
tab.value.document.response = res
|
||||
|
||||
tab.value.document.testResults = translateToSandboxTestResults(
|
||||
runResult.right
|
||||
)
|
||||
|
||||
@@ -549,13 +549,19 @@ const convertPathToHoppReqs = (
|
||||
),
|
||||
|
||||
// Construct request object
|
||||
RA.map(({ method, info }) =>
|
||||
makeRESTRequest({
|
||||
RA.map(({ method, info }) => {
|
||||
const openAPIUrl = parseOpenAPIUrl(doc)
|
||||
const openAPIPath = replaceOpenApiPathTemplating(pathName)
|
||||
|
||||
const endpoint =
|
||||
openAPIUrl.endsWith("/") && openAPIPath.startsWith("/")
|
||||
? openAPIUrl + openAPIPath.slice(1)
|
||||
: openAPIUrl + openAPIPath
|
||||
|
||||
return makeRESTRequest({
|
||||
name: info.operationId ?? info.summary ?? "Untitled Request",
|
||||
method: method.toUpperCase(),
|
||||
endpoint: `${parseOpenAPIUrl(doc)}${replaceOpenApiPathTemplating(
|
||||
pathName
|
||||
)}`,
|
||||
endpoint,
|
||||
|
||||
// We don't need to worry about reference types as the Dereferencing pass should remove them
|
||||
params: parseOpenAPIParams(
|
||||
@@ -572,7 +578,7 @@ const convertPathToHoppReqs = (
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
})
|
||||
),
|
||||
}),
|
||||
|
||||
// Disable Readonly
|
||||
RA.toArray
|
||||
|
||||
@@ -156,6 +156,7 @@ export default class ShortcodeListAdapter {
|
||||
const [shortcodeCreated$, shortcodeCreatedSub] = runAuthOnlyGQLSubscription(
|
||||
{
|
||||
query: ShortcodeCreatedDocument,
|
||||
variables: {},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -172,6 +173,7 @@ export default class ShortcodeListAdapter {
|
||||
const [shortcodeRevoked$, shortcodeRevokedSub] = runAuthOnlyGQLSubscription(
|
||||
{
|
||||
query: ShortcodeDeletedDocument,
|
||||
variables: {},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -188,6 +190,7 @@ export default class ShortcodeListAdapter {
|
||||
const [shortcodeUpdated$, shortcodeUpdatedSub] = runAuthOnlyGQLSubscription(
|
||||
{
|
||||
query: ShortcodeUpdatedDocument,
|
||||
variables: {},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -1034,6 +1034,11 @@ export default class NewTeamCollectionAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to obtain the inherited auth and headers for a given folder path, used for both REST and GraphQL team collections
|
||||
* @param folderPath the path of the folder to cascade the auth from
|
||||
* @returns the inherited auth and headers for the given folder path
|
||||
*/
|
||||
public cascadeParentCollectionForHeaderAuth(folderPath: string) {
|
||||
let auth: HoppInheritedProperty["auth"] = {
|
||||
parentID: folderPath ?? "",
|
||||
@@ -1080,7 +1085,7 @@ export default class NewTeamCollectionAdapter {
|
||||
authType: "inherit",
|
||||
authActive: true,
|
||||
}
|
||||
auth.parentID = [...path.slice(0, i + 1)].join("/")
|
||||
auth.parentID = path.slice(0, i + 1).join("/")
|
||||
auth.parentName = parentFolder.title
|
||||
}
|
||||
|
||||
@@ -1089,9 +1094,12 @@ export default class NewTeamCollectionAdapter {
|
||||
const parentFolderAuth = data.auth
|
||||
const parentFolderHeaders = data.headers
|
||||
|
||||
if (parentFolderAuth?.authType === "inherit" && path.length === 1) {
|
||||
if (
|
||||
parentFolderAuth?.authType === "inherit" &&
|
||||
path.slice(0, i + 1).length === 1
|
||||
) {
|
||||
auth = {
|
||||
parentID: [...path.slice(0, i + 1)].join("/"),
|
||||
parentID: path.slice(0, i + 1).join("/"),
|
||||
parentName: parentFolder.title,
|
||||
inheritedAuth: auth.inheritedAuth,
|
||||
}
|
||||
@@ -1099,7 +1107,7 @@ export default class NewTeamCollectionAdapter {
|
||||
|
||||
if (parentFolderAuth?.authType !== "inherit") {
|
||||
auth = {
|
||||
parentID: [...path.slice(0, i + 1)].join("/"),
|
||||
parentID: path.slice(0, i + 1).join("/"),
|
||||
parentName: parentFolder.title,
|
||||
inheritedAuth: parentFolderAuth,
|
||||
}
|
||||
@@ -1112,7 +1120,7 @@ export default class NewTeamCollectionAdapter {
|
||||
const index = headers.findIndex(
|
||||
(h) => h.inheritedHeader?.key === header.key
|
||||
)
|
||||
const currentPath = [...path.slice(0, i + 1)].join("/")
|
||||
const currentPath = path.slice(0, i + 1).join("/")
|
||||
if (index !== -1) {
|
||||
// Replace the existing header with the same key
|
||||
headers[index] = {
|
||||
|
||||
@@ -68,7 +68,7 @@ export function navigateToFolderWithIndexPath(
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to obtain the inherited auth and headers for a given folder path, used for both REST and GraphQL
|
||||
* Used to obtain the inherited auth and headers for a given folder path, used for both REST and GraphQL personal collections
|
||||
* @param folderPath the path of the folder to cascade the auth from
|
||||
* @param type the type of collection
|
||||
* @returns the inherited auth and headers for the given folder path
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
<template>
|
||||
<div class="flex flex-col justify-center">
|
||||
<div
|
||||
v-if="invalidLink"
|
||||
v-if="sharedRequestDetails.loading"
|
||||
class="flex justify-center items-center py-5"
|
||||
>
|
||||
<HoppSmartSpinner />
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else-if="E.isLeft(sharedRequestDetails.data) || invalidLink"
|
||||
class="flex flex-1 flex-col items-center justify-center p-8"
|
||||
>
|
||||
<icon-lucide-alert-triangle class="svg-icons mb-2 opacity-75" />
|
||||
@@ -12,8 +19,9 @@
|
||||
{{ t("error.invalid_embed_link") }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Embeds
|
||||
v-else-if="!invalidLink && tab"
|
||||
v-else-if="tab"
|
||||
v-model:modelTab="tab"
|
||||
:properties="properties"
|
||||
:shared-request-i-d="sharedRequestID"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@hoppscotch/selfhost-desktop",
|
||||
"private": true,
|
||||
"version": "2023.12.1-1",
|
||||
"version": "2023.12.2",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev:vite": "vite",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "hoppscotch-desktop"
|
||||
version = "23.12.1-1"
|
||||
version = "23.12.2"
|
||||
description = "A Tauri App"
|
||||
authors = ["you"]
|
||||
license = ""
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
},
|
||||
"package": {
|
||||
"productName": "Hoppscotch",
|
||||
"version": "23.12.1-1"
|
||||
"version": "23.12.2"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@hoppscotch/selfhost-web",
|
||||
"private": true,
|
||||
"version": "2023.12.1-1",
|
||||
"version": "2023.12.2",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev:vite": "vite",
|
||||
|
||||
@@ -114,6 +114,7 @@ async function setInitialUser() {
|
||||
} else {
|
||||
setUser(null)
|
||||
isGettingInitialUser.value = false
|
||||
await logout()
|
||||
}
|
||||
|
||||
return
|
||||
@@ -146,22 +147,26 @@ async function setInitialUser() {
|
||||
}
|
||||
|
||||
async function refreshToken() {
|
||||
const res = await axios.get(
|
||||
`${import.meta.env.VITE_BACKEND_API_URL}/auth/refresh`,
|
||||
{
|
||||
withCredentials: true,
|
||||
try {
|
||||
const res = await axios.get(
|
||||
`${import.meta.env.VITE_BACKEND_API_URL}/auth/refresh`,
|
||||
{
|
||||
withCredentials: true,
|
||||
}
|
||||
)
|
||||
|
||||
const isSuccessful = res.status === 200
|
||||
|
||||
if (isSuccessful) {
|
||||
authEvents$.next({
|
||||
event: "token_refresh",
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
const isSuccessful = res.status === 200
|
||||
|
||||
if (isSuccessful) {
|
||||
authEvents$.next({
|
||||
event: "token_refresh",
|
||||
})
|
||||
return isSuccessful
|
||||
} catch (error) {
|
||||
return false
|
||||
}
|
||||
|
||||
return isSuccessful
|
||||
}
|
||||
|
||||
async function sendMagicLink(email: string) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "hoppscotch-sh-admin",
|
||||
"private": true,
|
||||
"version": "2023.12.1-1",
|
||||
"version": "2023.12.2",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "pnpm exec npm-run-all -p -l dev:*",
|
||||
|
||||
59
packages/hoppscotch-sh-admin/src/components.d.ts
vendored
59
packages/hoppscotch-sh-admin/src/components.d.ts
vendored
@@ -1,9 +1,9 @@
|
||||
// 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';
|
||||
import '@vue/runtime-core'
|
||||
|
||||
export {};
|
||||
export {}
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
export interface GlobalComponents {
|
||||
@@ -13,8 +13,6 @@ declare module '@vue/runtime-core' {
|
||||
AppModal: typeof import('./components/app/Modal.vue')['default']
|
||||
AppSidebar: typeof import('./components/app/Sidebar.vue')['default']
|
||||
AppToast: typeof import('./components/app/Toast.vue')['default']
|
||||
ButtonPrimary: typeof import('./../../hoppscotch-ui/src/components/button/Primary.vue')['default']
|
||||
ButtonSecondary: typeof import('./../../hoppscotch-ui/src/components/button/Secondary.vue')['default']
|
||||
DashboardMetricsCard: typeof import('./components/dashboard/MetricsCard.vue')['default']
|
||||
HoppButtonPrimary: typeof import('@hoppscotch/ui')['HoppButtonPrimary']
|
||||
HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary']
|
||||
@@ -23,6 +21,7 @@ declare module '@vue/runtime-core' {
|
||||
HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal']
|
||||
HoppSmartInput: typeof import('@hoppscotch/ui')['HoppSmartInput']
|
||||
HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem']
|
||||
HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink']
|
||||
HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal']
|
||||
HoppSmartPicture: typeof import('@hoppscotch/ui')['HoppSmartPicture']
|
||||
HoppSmartSpinner: typeof import('@hoppscotch/ui')['HoppSmartSpinner']
|
||||
@@ -30,42 +29,13 @@ declare module '@vue/runtime-core' {
|
||||
HoppSmartTable: typeof import('@hoppscotch/ui')['HoppSmartTable']
|
||||
HoppSmartTabs: typeof import('@hoppscotch/ui')['HoppSmartTabs']
|
||||
HoppSmartToggle: typeof import('@hoppscotch/ui')['HoppSmartToggle']
|
||||
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
|
||||
IconLucideChevronDown: typeof import('~icons/lucide/chevron-down')['default']
|
||||
IconLucideInbox: typeof import('~icons/lucide/inbox')['default']
|
||||
IconLucideUser: typeof import('~icons/lucide/user')['default']
|
||||
SettingsAuthProvider: typeof import('./components/settings/AuthProvider.vue')['default']
|
||||
SettingsConfigurations: typeof import('./components/settings/Configurations.vue')['default']
|
||||
SettingsReset: typeof import('./components/settings/Reset.vue')['default']
|
||||
SettingsServerRestart: typeof import('./components/settings/ServerRestart.vue')['default']
|
||||
SettingsSmtpConfiguration: typeof import('./components/settings/SmtpConfiguration.vue')['default']
|
||||
SmartAnchor: typeof import('./../../hoppscotch-ui/src/components/smart/Anchor.vue')['default']
|
||||
SmartAutoComplete: typeof import('./../../hoppscotch-ui/src/components/smart/AutoComplete.vue')['default']
|
||||
SmartCheckbox: typeof import('./../../hoppscotch-ui/src/components/smart/Checkbox.vue')['default']
|
||||
SmartConfirmModal: typeof import('./../../hoppscotch-ui/src/components/smart/ConfirmModal.vue')['default']
|
||||
SmartExpand: typeof import('./../../hoppscotch-ui/src/components/smart/Expand.vue')['default']
|
||||
SmartFileChip: typeof import('./../../hoppscotch-ui/src/components/smart/FileChip.vue')['default']
|
||||
SmartInput: typeof import('./../../hoppscotch-ui/src/components/smart/Input.vue')['default']
|
||||
SmartIntersection: typeof import('./../../hoppscotch-ui/src/components/smart/Intersection.vue')['default']
|
||||
SmartItem: typeof import('./../../hoppscotch-ui/src/components/smart/Item.vue')['default']
|
||||
SmartLink: typeof import('./../../hoppscotch-ui/src/components/smart/Link.vue')['default']
|
||||
SmartModal: typeof import('./../../hoppscotch-ui/src/components/smart/Modal.vue')['default']
|
||||
SmartPicture: typeof import('./../../hoppscotch-ui/src/components/smart/Picture.vue')['default']
|
||||
SmartPlaceholder: typeof import('./../../hoppscotch-ui/src/components/smart/Placeholder.vue')['default']
|
||||
SmartProgressRing: typeof import('./../../hoppscotch-ui/src/components/smart/ProgressRing.vue')['default']
|
||||
SmartRadio: typeof import('./../../hoppscotch-ui/src/components/smart/Radio.vue')['default']
|
||||
SmartRadioGroup: typeof import('./../../hoppscotch-ui/src/components/smart/RadioGroup.vue')['default']
|
||||
SmartSelectWrapper: typeof import('./../../hoppscotch-ui/src/components/smart/SelectWrapper.vue')['default']
|
||||
SmartSlideOver: typeof import('./../../hoppscotch-ui/src/components/smart/SlideOver.vue')['default']
|
||||
SmartSpinner: typeof import('./../../hoppscotch-ui/src/components/smart/Spinner.vue')['default']
|
||||
SmartTab: typeof import('./../../hoppscotch-ui/src/components/smart/Tab.vue')['default']
|
||||
SmartTable: typeof import('./../../hoppscotch-ui/src/components/smart/Table.vue')['default']
|
||||
SmartTabs: typeof import('./../../hoppscotch-ui/src/components/smart/Tabs.vue')['default']
|
||||
SmartToggle: typeof import('./../../hoppscotch-ui/src/components/smart/Toggle.vue')['default']
|
||||
SmartTree: typeof import('./../../hoppscotch-ui/src/components/smart/Tree.vue')['default']
|
||||
SmartTreeBranch: typeof import('./../../hoppscotch-ui/src/components/smart/TreeBranch.vue')['default']
|
||||
SmartWindow: typeof import('./../../hoppscotch-ui/src/components/smart/Window.vue')['default']
|
||||
SmartWindows: typeof import('./../../hoppscotch-ui/src/components/smart/Windows.vue')['default']
|
||||
TeamsAdd: typeof import('./components/teams/Add.vue')['default']
|
||||
TeamsDetails: typeof import('./components/teams/Details.vue')['default']
|
||||
TeamsInvite: typeof import('./components/teams/Invite.vue')['default']
|
||||
@@ -76,27 +46,6 @@ declare module '@vue/runtime-core' {
|
||||
UsersDetails: typeof import('./components/users/Details.vue')['default']
|
||||
UsersInviteModal: typeof import('./components/users/InviteModal.vue')['default']
|
||||
UsersSharedRequests: typeof import('./components/users/SharedRequests.vue')['default']
|
||||
AppHeader: typeof import('./components/app/Header.vue')['default'];
|
||||
AppLogin: typeof import('./components/app/Login.vue')['default'];
|
||||
AppLogout: typeof import('./components/app/Logout.vue')['default'];
|
||||
AppModal: typeof import('./components/app/Modal.vue')['default'];
|
||||
AppSidebar: typeof import('./components/app/Sidebar.vue')['default'];
|
||||
AppToast: typeof import('./components/app/Toast.vue')['default'];
|
||||
DashboardMetricsCard: typeof import('./components/dashboard/MetricsCard.vue')['default'];
|
||||
HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary'];
|
||||
HoppSmartAnchor: typeof import('@hoppscotch/ui')['HoppSmartAnchor'];
|
||||
HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal'];
|
||||
HoppSmartInput: typeof import('@hoppscotch/ui')['HoppSmartInput'];
|
||||
HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem'];
|
||||
HoppSmartPicture: typeof import('@hoppscotch/ui')['HoppSmartPicture'];
|
||||
HoppSmartSpinner: typeof import('@hoppscotch/ui')['HoppSmartSpinner'];
|
||||
IconLucideInbox: typeof import('~icons/lucide/inbox')['default'];
|
||||
TeamsAdd: typeof import('./components/teams/Add.vue')['default'];
|
||||
TeamsDetails: typeof import('./components/teams/Details.vue')['default'];
|
||||
TeamsInvite: typeof import('./components/teams/Invite.vue')['default'];
|
||||
TeamsMembers: typeof import('./components/teams/Members.vue')['default'];
|
||||
TeamsPendingInvites: typeof import('./components/teams/PendingInvites.vue')['default'];
|
||||
Tippy: typeof import('vue-tippy')['Tippy'];
|
||||
UsersInviteModal: typeof import('./components/users/InviteModal.vue')['default'];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
:class="isOpen ? '' : '-translate-x-full ease-in'"
|
||||
class="sidebar-container transform !md:translate-x-0 ease-out"
|
||||
>
|
||||
<div :class="isExpanded ? 'w-80' : 'w-full'">
|
||||
<div :class="isExpanded ? 'w-56' : 'w-full'">
|
||||
<div class="flex items-center justify-start px-4 my-4">
|
||||
<div class="flex items-center">
|
||||
<HoppSmartLink class="flex items-center space-x-4" to="/dashboard">
|
||||
@@ -26,7 +26,6 @@
|
||||
</HoppSmartLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="my-5">
|
||||
<HoppSmartLink
|
||||
v-for="(navigation, index) in primaryNavigations"
|
||||
@@ -39,19 +38,32 @@
|
||||
:to="navigation.to"
|
||||
tabindex="0"
|
||||
:exact="navigation.exact"
|
||||
class="nav-link"
|
||||
:class="
|
||||
!isExpanded
|
||||
? 'flex items-center justify-center'
|
||||
: 'flex items-center'
|
||||
"
|
||||
@click="setActiveTab(navigation.label)"
|
||||
>
|
||||
<div v-if="navigation.icon">
|
||||
<component :is="navigation.icon" class="svg-icons" />
|
||||
<div
|
||||
class="flex p-5 w-full font-bold"
|
||||
:class="
|
||||
activeTab === navigation.label
|
||||
? 'bg-primaryDark text-secondaryDark border-l-2 border-l-emerald-600'
|
||||
: 'bg-primary hover:bg-primaryLight hover:text-secondaryDark focus-visible:text-secondaryDark focus-visible:bg-primaryLight focus-visible:outline-none'
|
||||
"
|
||||
>
|
||||
<div
|
||||
v-if="navigation.icon"
|
||||
class="svg-icons"
|
||||
:class="isExpanded ? 'mr-3' : 'mx-auto'"
|
||||
>
|
||||
<component :is="navigation.icon" />
|
||||
</div>
|
||||
<span v-if="isExpanded" class="nav-title">
|
||||
{{ navigation.label }}
|
||||
</span>
|
||||
</div>
|
||||
<span v-if="isExpanded" class="nav-title">
|
||||
{{ navigation.label }}
|
||||
</span>
|
||||
</HoppSmartLink>
|
||||
</nav>
|
||||
</div>
|
||||
@@ -60,19 +72,27 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { HoppSmartLink } from '@hoppscotch/ui';
|
||||
import { ref, type Component } from 'vue';
|
||||
|
||||
import { useI18n } from '~/composables/i18n';
|
||||
import { useSidebar } from '~/composables/useSidebar';
|
||||
import IconDashboard from '~icons/lucide/layout-dashboard';
|
||||
import IconSettings from '~icons/lucide/settings';
|
||||
import IconUser from '~icons/lucide/user';
|
||||
import IconUsers from '~icons/lucide/users';
|
||||
import IconSettings from '~icons/lucide/settings';
|
||||
import { useI18n } from '~/composables/i18n';
|
||||
|
||||
const t = useI18n();
|
||||
|
||||
const { isOpen, isExpanded } = useSidebar();
|
||||
|
||||
const primaryNavigations = [
|
||||
type NavigationItem = {
|
||||
label: string;
|
||||
icon: Component;
|
||||
to: string;
|
||||
exact: boolean;
|
||||
};
|
||||
|
||||
const primaryNavigations: NavigationItem[] = [
|
||||
{
|
||||
label: t('metrics.dashboard'),
|
||||
icon: IconDashboard,
|
||||
@@ -98,6 +118,12 @@ const primaryNavigations = [
|
||||
exact: true,
|
||||
},
|
||||
];
|
||||
|
||||
const activeTab = ref('Dashboard');
|
||||
|
||||
const setActiveTab = (tab: string) => {
|
||||
activeTab.value = tab;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -106,54 +132,4 @@ const primaryNavigations = [
|
||||
@apply transition duration-300;
|
||||
@apply flex overflow-y-auto bg-primary border-r border-divider;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
@apply relative;
|
||||
@apply p-4;
|
||||
@apply flex flex-1;
|
||||
@apply items-center;
|
||||
@apply space-x-4;
|
||||
@apply hover:bg-primaryDark hover:text-secondaryDark;
|
||||
@apply focus-visible:text-secondaryDark;
|
||||
@apply after:absolute;
|
||||
@apply after:inset-x-0;
|
||||
@apply after:md:inset-x-auto;
|
||||
@apply after:md:inset-y-0;
|
||||
@apply after:bottom-0;
|
||||
@apply after:md:bottom-auto;
|
||||
@apply after:md:left-0;
|
||||
@apply after:z-10;
|
||||
@apply after:h-0.5;
|
||||
@apply after:md:h-full;
|
||||
@apply after:w-full;
|
||||
@apply after:md:w-0.5;
|
||||
@apply after:content-[''];
|
||||
@apply focus:after:bg-divider;
|
||||
|
||||
.svg-icons {
|
||||
@apply opacity-75;
|
||||
}
|
||||
|
||||
&.router-link-active {
|
||||
@apply text-secondaryDark;
|
||||
@apply bg-primaryLight;
|
||||
@apply hover:text-secondaryDark;
|
||||
@apply after:bg-accent;
|
||||
|
||||
.svg-icons {
|
||||
@apply opacity-100;
|
||||
}
|
||||
}
|
||||
|
||||
&.exact-active-link {
|
||||
@apply text-secondaryDark;
|
||||
@apply bg-primaryLight;
|
||||
@apply hover:text-secondaryDark;
|
||||
@apply after:bg-accent;
|
||||
|
||||
.svg-icons {
|
||||
@apply opacity-100;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:18-alpine3.16 as base_builder
|
||||
FROM node:18-alpine3.19 as base_builder
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
|
||||
Reference in New Issue
Block a user