Compare commits
12 Commits
feat/share
...
fix/embed-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a267e9c411 | ||
|
|
b53ae0cefe | ||
|
|
ebf90207e5 | ||
|
|
4ac8a117ef | ||
|
|
c1bc430ee6 | ||
|
|
9201aa7d7d | ||
|
|
87395a4553 | ||
|
|
6063c633ee | ||
|
|
7481feb366 | ||
|
|
bdfa14fa54 | ||
|
|
0a61ec2bfe | ||
|
|
2bf0106aa2 |
@@ -32,6 +32,9 @@
|
||||
"lint-staged": "12.4.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"vue": "3.3.9"
|
||||
},
|
||||
"packageExtensions": {
|
||||
"httpsnippet@^3.0.1": {
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -147,7 +147,7 @@ module.exports = {
|
||||
// The glob patterns Jest uses to detect test files
|
||||
testMatch: [
|
||||
// "**/__tests__/**/*.[jt]s?(x)",
|
||||
"**/src/__tests__/**/*.*.ts",
|
||||
"**/src/__tests__/commands/**/*.*.ts",
|
||||
],
|
||||
|
||||
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
|
||||
|
||||
@@ -19,8 +19,9 @@
|
||||
"debugger": "node debugger.js 9999",
|
||||
"prepublish": "pnpm exec tsup",
|
||||
"prettier-format": "prettier --config .prettierrc 'src/**/*.ts' --write",
|
||||
"test": "pnpm run build && jest && rm -rf dist",
|
||||
"do-typecheck": "pnpm exec tsc --noEmit",
|
||||
"test": "pnpm run build && jest && rm -rf dist"
|
||||
"do-test": "pnpm test"
|
||||
},
|
||||
"keywords": [
|
||||
"cli",
|
||||
|
||||
@@ -28,7 +28,7 @@ describe("Test 'hopp test <file>' command:", () => {
|
||||
|
||||
expect(out).toBe<HoppErrorCode>("UNKNOWN_ERROR");
|
||||
});
|
||||
|
||||
|
||||
test("Malformed collection file.", async () => {
|
||||
const cmd = `node ./bin/hopp test ${getTestJsonFilePath(
|
||||
"malformed-collection2.json"
|
||||
@@ -106,7 +106,7 @@ describe("Test 'hopp test <file> --env <file>' command:", () => {
|
||||
const TESTS_PATH = getTestJsonFilePath("env-flag-tests.json");
|
||||
const ENV_PATH = getTestJsonFilePath("env-flag-envs.json");
|
||||
const cmd = `node ./bin/hopp test ${TESTS_PATH} --env ${ENV_PATH}`;
|
||||
const { error } = await execAsync(cmd);
|
||||
const { error, stdout } = await execAsync(cmd);
|
||||
|
||||
expect(error).toBeNull();
|
||||
});
|
||||
@@ -129,7 +129,6 @@ describe("Test 'hopp test <file> --delay <delay_in_ms>' command:", () => {
|
||||
const cmd = `${VALID_TEST_CMD} --delay 'NaN'`;
|
||||
const { stderr } = await execAsync(cmd);
|
||||
const out = getErrorCode(stderr);
|
||||
console.log("invalid value thing", out)
|
||||
expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"URL": "https://echo.hoppscotch.io",
|
||||
"HOST": "echo.hoppscotch.io",
|
||||
"X-COUNTRY": "IN",
|
||||
"BODY_VALUE": "body_value",
|
||||
"BODY_KEY": "body_key"
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"method": "POST",
|
||||
"auth": { "authType": "none", "authActive": true },
|
||||
"preRequestScript": "",
|
||||
"testScript": "const HOST = pw.env.get(\"HOST\");\nconst UNSET_ENV = pw.env.get(\"UNSET_ENV\");\nconst EXPECTED_URL = \"https://echo.hoppscotch.io\";\nconst URL = pw.env.get(\"URL\");\nconst X_COUNTRY = pw.env.get(\"X-COUNTRY\");\nconst BODY_VALUE = pw.env.get(\"BODY_VALUE\");\n\n// Check JSON response property\npw.test(\"Check headers properties.\", ()=> {\n pw.expect(pw.response.body.headers.host).toBe(HOST);\n\t pw.expect(pw.response.body.headers[\"x-country\"]).toBe(X_COUNTRY); \n});\n\npw.test(\"Check data properties.\", () => {\n\tconst DATA = pw.response.body.data;\n \n pw.expect(DATA).toBeType(\"string\");\n pw.expect(JSON.parse(DATA).body_key).toBe(BODY_VALUE);\n});\n\npw.test(\"Check request URL.\", () => {\n pw.expect(URL).toBe(EXPECTED_URL);\n})\n\npw.test(\"Check unset ENV.\", () => {\n pw.expect(UNSET_ENV).toBeType(\"undefined\");\n})",
|
||||
"testScript": "const HOST = pw.env.get(\"HOST\");\nconst UNSET_ENV = pw.env.get(\"UNSET_ENV\");\nconst EXPECTED_URL = \"https://echo.hoppscotch.io\";\nconst URL = pw.env.get(\"URL\");\nconst BODY_VALUE = pw.env.get(\"BODY_VALUE\");\n\n// Check JSON response property\npw.test(\"Check headers properties.\", ()=> {\n pw.expect(pw.response.body.headers.host).toBe(HOST);\n});\n\npw.test(\"Check data properties.\", () => {\n\tconst DATA = pw.response.body.data;\n \n pw.expect(DATA).toBeType(\"string\");\n pw.expect(JSON.parse(DATA).body_key).toBe(BODY_VALUE);\n});\n\npw.test(\"Check request URL.\", () => {\n pw.expect(URL).toBe(EXPECTED_URL);\n})\n\npw.test(\"Check unset ENV.\", () => {\n pw.expect(UNSET_ENV).toBeType(\"undefined\");\n})",
|
||||
"body": {
|
||||
"contentType": "application/json",
|
||||
"body": "{\n \"<<BODY_KEY>>\":\"<<BODY_VALUE>>\"\n}"
|
||||
|
||||
@@ -6,23 +6,24 @@ import {
|
||||
parseTemplateString,
|
||||
parseTemplateStringE,
|
||||
} from "@hoppscotch/data";
|
||||
import { runPreRequestScript } from "@hoppscotch/js-sandbox";
|
||||
import { flow, pipe } from "fp-ts/function";
|
||||
import * as TE from "fp-ts/TaskEither";
|
||||
import * as E from "fp-ts/Either";
|
||||
import * as RA from "fp-ts/ReadonlyArray";
|
||||
import { runPreRequestScript } from "@hoppscotch/js-sandbox/node";
|
||||
import * as A from "fp-ts/Array";
|
||||
import * as E from "fp-ts/Either";
|
||||
import * as O from "fp-ts/Option";
|
||||
import * as RA from "fp-ts/ReadonlyArray";
|
||||
import * as TE from "fp-ts/TaskEither";
|
||||
import { flow, pipe } from "fp-ts/function";
|
||||
import * as S from "fp-ts/string";
|
||||
import qs from "qs";
|
||||
|
||||
import { EffectiveHoppRESTRequest } from "../interfaces/request";
|
||||
import { error, HoppCLIError } from "../types/errors";
|
||||
import { HoppCLIError, error } from "../types/errors";
|
||||
import { HoppEnvs } from "../types/request";
|
||||
import { isHoppCLIError } from "./checks";
|
||||
import { tupleToRecord, arraySort, arrayFlatMap } from "./functions/array";
|
||||
import { toFormData } from "./mutators";
|
||||
import { getEffectiveFinalMetaData } from "./getters";
|
||||
import { PreRequestMetrics } from "../types/response";
|
||||
import { isHoppCLIError } from "./checks";
|
||||
import { arrayFlatMap, arraySort, tupleToRecord } from "./functions/array";
|
||||
import { getEffectiveFinalMetaData } from "./getters";
|
||||
import { toFormData } from "./mutators";
|
||||
|
||||
/**
|
||||
* Runs pre-request-script runner over given request which extracts set ENVs and
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import { HoppRESTRequest } from "@hoppscotch/data";
|
||||
import { execTestScript, TestDescriptor } from "@hoppscotch/js-sandbox";
|
||||
import { hrtime } from "process";
|
||||
import { flow, pipe } from "fp-ts/function";
|
||||
import * as RA from "fp-ts/ReadonlyArray";
|
||||
import { TestDescriptor } from "@hoppscotch/js-sandbox";
|
||||
import { runTestScript } from "@hoppscotch/js-sandbox/node";
|
||||
import * as A from "fp-ts/Array";
|
||||
import * as TE from "fp-ts/TaskEither";
|
||||
import * as RA from "fp-ts/ReadonlyArray";
|
||||
import * as T from "fp-ts/Task";
|
||||
import * as TE from "fp-ts/TaskEither";
|
||||
import { flow, pipe } from "fp-ts/function";
|
||||
import { hrtime } from "process";
|
||||
|
||||
import {
|
||||
RequestRunnerResponse,
|
||||
TestReport,
|
||||
TestScriptParams,
|
||||
} from "../interfaces/response";
|
||||
import { error, HoppCLIError } from "../types/errors";
|
||||
import { HoppCLIError, error } from "../types/errors";
|
||||
import { HoppEnvs } from "../types/request";
|
||||
import { ExpectResult, TestMetrics, TestRunnerRes } from "../types/response";
|
||||
import { getDurationInSeconds } from "./getters";
|
||||
@@ -36,7 +38,7 @@ export const testRunner = (
|
||||
pipe(
|
||||
TE.of(testScriptData),
|
||||
TE.chain(({ testScript, response, envs }) =>
|
||||
execTestScript(testScript, envs, response)
|
||||
runTestScript(testScript, envs, response)
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width=".88em" height="1em" viewBox="0 0 21 24" class="iconify iconify--fontisto"><path fill="currentColor" d="M12.731 2.751 17.666 5.6a2.138 2.138 0 1 1 2.07 3.548l-.015.003v5.7a2.14 2.14 0 1 1-2.098 3.502l-.002-.002-4.905 2.832a2.14 2.14 0 1 1-4.079.054l-.004.015-4.941-2.844a2.14 2.14 0 1 1-2.067-3.556l.015-.003V9.15a2.14 2.14 0 1 1 1.58-3.926l-.01-.005c.184.106.342.231.479.376l.001.001 4.938-2.85a2.14 2.14 0 1 1 4.096.021l.004-.015zm-.515.877a.766.766 0 0 1-.057.057l-.001.001 6.461 11.19c.026-.009.056-.016.082-.023V9.146a2.14 2.14 0 0 1-1.555-2.603l-.003.015.019-.072zm-3.015.059-.06-.06-4.946 2.852A2.137 2.137 0 0 1 2.749 9.12l-.015.004-.076.021v5.708l.084.023 6.461-11.19zm2.076.507a2.164 2.164 0 0 1-1.207-.004l.015.004-6.46 11.189c.286.276.496.629.597 1.026l.003.015h12.911c.102-.413.313-.768.599-1.043l.001-.001L11.28 4.194zm.986 16.227 4.917-2.838a1.748 1.748 0 0 1-.038-.142H4.222l-.021.083 4.939 2.852c.39-.403.936-.653 1.54-.653.626 0 1.189.268 1.581.696l.001.002z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width=".88em" height="1em" viewBox="0 0 21 24"><path fill="currentColor" d="M12.731 2.751 17.666 5.6a2.138 2.138 0 1 1 2.07 3.548l-.015.003v5.7a2.14 2.14 0 1 1-2.098 3.502l-.002-.002-4.905 2.832a2.14 2.14 0 1 1-4.079.054l-.004.015-4.941-2.844a2.14 2.14 0 1 1-2.067-3.556l.015-.003V9.15a2.14 2.14 0 1 1 1.58-3.926l-.01-.005c.184.106.342.231.479.376l.001.001 4.938-2.85a2.14 2.14 0 1 1 4.096.021l.004-.015zm-.515.877a.766.766 0 0 1-.057.057l-.001.001 6.461 11.19c.026-.009.056-.016.082-.023V9.146a2.14 2.14 0 0 1-1.555-2.603l-.003.015.019-.072zm-3.015.059-.06-.06-4.946 2.852A2.137 2.137 0 0 1 2.749 9.12l-.015.004-.076.021v5.708l.084.023 6.461-11.19zm2.076.507a2.164 2.164 0 0 1-1.207-.004l.015.004-6.46 11.189c.286.276.496.629.597 1.026l.003.015h12.911c.102-.413.313-.768.599-1.043l.001-.001L11.28 4.194zm.986 16.227 4.917-2.838a1.748 1.748 0 0 1-.038-.142H4.222l-.021.083 4.939 2.852c.39-.403.936-.653 1.54-.653.626 0 1.189.268 1.581.696l.001.002z"/></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1017 B |
1
packages/hoppscotch-common/assets/icons/mqtt.svg
Normal file
1
packages/hoppscotch-common/assets/icons/mqtt.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 16 16"><path fill="currentColor" fill-rule="evenodd" d="M10.133 1h4.409a.5.5 0 0 1 .5.5v4.422c0 .026-.035.033-.045.01l-.048-.112a9.095 9.095 0 0 0-4.825-4.776c-.023-.01-.016-.044.01-.044Zm-8.588.275h-.5v1h.5c7.027 0 12.229 5.199 12.229 12.226v.5h1v-.5c0-7.58-5.65-13.226-13.229-13.226Zm.034 4.22h-.5v1h.5c2.361 0 4.348.837 5.744 2.238 1.395 1.401 2.227 3.395 2.227 5.758v.5h1v-.5c0-2.604-.921-4.859-2.52-6.463-1.596-1.605-3.845-2.532-6.45-2.532Zm-.528 8.996v-4.423c0-.041.033-.074.074-.074a4.923 4.923 0 0 1 4.923 4.922.074.074 0 0 1-.074.074H1.551a.5.5 0 0 1-.5-.5Z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 684 B |
1
packages/hoppscotch-common/assets/icons/socketio.svg
Normal file
1
packages/hoppscotch-common/assets/icons/socketio.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 16 16"><path fill="currentColor" fill-rule="evenodd" d="M9.277 2.084a.5.5 0 0 1 .185.607l-2.269 5.5a.5.5 0 0 1-.462.309H3.5a.5.5 0 0 1-.354-.854l5.5-5.5a.5.5 0 0 1 .631-.062ZM4.707 7.5h1.69l1.186-2.875L4.707 7.5Zm2.016 6.416a.5.5 0 0 1-.185-.607l2.269-5.5a.5.5 0 0 1 .462-.309H12.5a.5.5 0 0 1 .354.854l-5.5 5.5a.5.5 0 0 1-.631.062Zm4.57-5.416h-1.69l-1.186 2.875L11.293 8.5Z" clip-rule="evenodd"/><path fill="currentColor" fill-rule="evenodd" d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0Zm-1 0A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 633 B |
1
packages/hoppscotch-common/assets/icons/websocket.svg
Normal file
1
packages/hoppscotch-common/assets/icons/websocket.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 16 16"><path fill="currentColor" d="M1 2h4.257a2.5 2.5 0 0 1 1.768.732L9.293 5 5 9.293 3.732 8.025A2.5 2.5 0 0 1 3 6.257V4H2v2.257a3.5 3.5 0 0 0 1.025 2.475L5 10.707l1.25-1.25 2.396 2.397.708-.708L6.957 8.75 8.75 6.957l2.396 2.397.708-.708L9.457 6.25 10.707 5 7.732 2.025A3.5 3.5 0 0 0 5.257 1H1v1ZM10.646 2.354l2.622 2.62A2.5 2.5 0 0 1 14 6.744V12h1V6.743a3.5 3.5 0 0 0-1.025-2.475l-2.621-2.622-.707.708ZM4.268 13.975l-2.622-2.621.708-.708 2.62 2.622A2.5 2.5 0 0 0 6.744 14H15v1H6.743a3.5 3.5 0 0 1-2.475-1.025Z"/></svg>
|
||||
|
After Width: | Height: | Size: 610 B |
@@ -96,6 +96,7 @@
|
||||
"keyboard_shortcuts": "Keyboard shortcuts",
|
||||
"name": "Hoppscotch",
|
||||
"new_version_found": "New version found. Refresh to update.",
|
||||
"open_in_hoppscotch": "Open in Hoppscotch",
|
||||
"options": "Options",
|
||||
"proxy_privacy_policy": "Proxy privacy policy",
|
||||
"reload": "Reload",
|
||||
@@ -431,8 +432,9 @@
|
||||
"close_unsaved_tab": "You have unsaved changes",
|
||||
"collections": "Collections",
|
||||
"confirm": "Confirm",
|
||||
"customize_request": "Customize Request",
|
||||
"edit_request": "Edit Request",
|
||||
"share_request":"Share Request",
|
||||
"share_request": "Share Request",
|
||||
"import_export": "Import / Export"
|
||||
},
|
||||
"mqtt": {
|
||||
@@ -621,29 +623,30 @@
|
||||
"additional": "Additional Settings",
|
||||
"verify_email": "Verify email"
|
||||
},
|
||||
"shared_requests":{
|
||||
"button":"Button",
|
||||
"shared_requests": {
|
||||
"button": "Button",
|
||||
"button_info": "Create a 'Run in Hoppscotch' button for your website, blog or a README.",
|
||||
"customize": "Customize",
|
||||
"creating_widget": "Creating widget",
|
||||
"copy_html": "Copy HTML",
|
||||
"copy_link": "Copy Link",
|
||||
"copy_markdown": "Copy Markdown",
|
||||
"deleted":"Shared request deleted",
|
||||
"deleted": "Shared request deleted",
|
||||
"description": "Select a widget, you can change and customize this later",
|
||||
"embed":"Embed",
|
||||
"embed": "Embed",
|
||||
"embed_info": "Add a mini 'Hoppscotch API Playground' to your website, blog or documentation.",
|
||||
"link":"Link",
|
||||
"link": "Link",
|
||||
"link_info": "Create a shareable link to share with anyone on the internet with view access.",
|
||||
"not_found":"Shared request not found",
|
||||
"modified": "Shared request modified",
|
||||
"not_found": "Shared request not found",
|
||||
"open_new_tab": "Open in new tab",
|
||||
"preview":"Preview",
|
||||
"run_in_hoppscotch":"Run in Hoppscotch",
|
||||
"theme":{
|
||||
"dark":"Dark",
|
||||
"light":"Light",
|
||||
"system" :"System",
|
||||
"title":"Theme"
|
||||
"preview": "Preview",
|
||||
"run_in_hoppscotch": "Run in Hoppscotch",
|
||||
"theme": {
|
||||
"dark": "Dark",
|
||||
"light": "Light",
|
||||
"system": "System",
|
||||
"title": "Theme"
|
||||
}
|
||||
},
|
||||
"shortcut": {
|
||||
@@ -689,7 +692,7 @@
|
||||
"save_to_collections": "Save to Collections",
|
||||
"send_request": "Send Request",
|
||||
"show_code": "Generate code snippet",
|
||||
"share_request":"Share Request",
|
||||
"share_request": "Share Request",
|
||||
"title": "Request"
|
||||
},
|
||||
"response": {
|
||||
|
||||
@@ -61,6 +61,7 @@ declare module 'vue' {
|
||||
CollectionsTeamCollections: typeof import('./components/collections/TeamCollections.vue')['default']
|
||||
CookiesAllModal: typeof import('./components/cookies/AllModal.vue')['default']
|
||||
CookiesEditCookie: typeof import('./components/cookies/EditCookie.vue')['default']
|
||||
Embeds: typeof import('./components/embeds/index.vue')['default']
|
||||
Environments: typeof import('./components/environments/index.vue')['default']
|
||||
EnvironmentsAdd: typeof import('./components/environments/Add.vue')['default']
|
||||
EnvironmentsImportExport: typeof import('./components/environments/ImportExport.vue')['default']
|
||||
@@ -163,6 +164,12 @@ declare module 'vue' {
|
||||
IconLucideUsers: typeof import('~icons/lucide/users')['default']
|
||||
IconLucideVerified: typeof import('~icons/lucide/verified')['default']
|
||||
IconLucideX: typeof import('~icons/lucide/x')['default']
|
||||
ImportExportBase: typeof import('./components/importExport/Base.vue')['default']
|
||||
ImportExportImportExportList: typeof import('./components/importExport/ImportExportList.vue')['default']
|
||||
ImportExportImportExportSourcesList: typeof import('./components/importExport/ImportExportSourcesList.vue')['default']
|
||||
ImportExportImportExportStepsFileImport: typeof import('./components/importExport/ImportExportSteps/FileImport.vue')['default']
|
||||
ImportExportImportExportStepsMyCollectionImport: typeof import('./components/importExport/ImportExportSteps/MyCollectionImport.vue')['default']
|
||||
ImportExportImportExportStepsUrlImport: typeof import('./components/importExport/ImportExportSteps/UrlImport.vue')['default']
|
||||
InterceptorsErrorPlaceholder: typeof import('./components/interceptors/ErrorPlaceholder.vue')['default']
|
||||
InterceptorsExtensionSubtitle: typeof import('./components/interceptors/ExtensionSubtitle.vue')['default']
|
||||
LensesHeadersRenderer: typeof import('./components/lenses/HeadersRenderer.vue')['default']
|
||||
|
||||
@@ -305,10 +305,9 @@ watch(isOnline, () => {
|
||||
if (!isOnline.value) {
|
||||
bannerID = banner.showBanner(offlineBanner)
|
||||
return
|
||||
} else {
|
||||
if (banner.content && bannerID) {
|
||||
banner.removeBanner(bannerID)
|
||||
}
|
||||
}
|
||||
if (banner.content && bannerID) {
|
||||
banner.removeBanner(bannerID)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -17,9 +17,10 @@
|
||||
v-if="isEmpty(shortcutsResults)"
|
||||
:text="`${t('state.nothing_found')} ‟${filterText}”`"
|
||||
>
|
||||
<icon-lucide-search class="svg-icons pb-2 opacity-75" />
|
||||
<template #icon>
|
||||
<icon-lucide-search class="svg-icons opacity-75" />
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
|
||||
<details
|
||||
v-for="(sectionResults, sectionTitle) in shortcutsResults"
|
||||
v-else
|
||||
|
||||
@@ -49,13 +49,15 @@
|
||||
:text="`${t('state.nothing_found')} ‟${search}”`"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-lucide-search class="svg-icons pb-2 opacity-75" />
|
||||
<icon-lucide-search class="svg-icons opacity-75" />
|
||||
</template>
|
||||
<template #body>
|
||||
<HoppButtonSecondary
|
||||
:label="t('action.clear')"
|
||||
outline
|
||||
@click="search = ''"
|
||||
/>
|
||||
</template>
|
||||
<HoppButtonSecondary
|
||||
:label="t('action.clear')"
|
||||
outline
|
||||
@click="search = ''"
|
||||
/>
|
||||
</HoppSmartPlaceholder>
|
||||
</div>
|
||||
<div
|
||||
@@ -112,6 +114,7 @@ import {
|
||||
WorkspaceSpotlightSearcherService,
|
||||
} from "~/services/spotlight/searchers/workspace.searcher"
|
||||
import { InterceptorSpotlightSearcherService } from "~/services/spotlight/searchers/interceptor.searcher"
|
||||
import { platform } from "~/platform"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
@@ -141,6 +144,10 @@ useService(WorkspaceSpotlightSearcherService)
|
||||
useService(SwitchWorkspaceSpotlightSearcherService)
|
||||
useService(InterceptorSpotlightSearcherService)
|
||||
|
||||
platform.spotlight?.additionalSearchers?.forEach((searcher) =>
|
||||
useService(searcher)
|
||||
)
|
||||
|
||||
const search = ref("")
|
||||
|
||||
const searchSession = ref<SpotlightSearchState>()
|
||||
|
||||
@@ -254,7 +254,7 @@
|
||||
:text="`${t('state.nothing_found')} ‟${filterText}”`"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-lucide-search class="svg-icons pb-2 opacity-75" />
|
||||
<icon-lucide-search class="svg-icons opacity-75" />
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
<HoppSmartPlaceholder
|
||||
@@ -263,27 +263,29 @@
|
||||
:alt="`${t('empty.collections')}`"
|
||||
:text="t('empty.collections')"
|
||||
>
|
||||
<div class="flex flex-col items-center space-y-4">
|
||||
<span class="text-center text-secondaryLight">
|
||||
{{ t("collection.import_or_create") }}
|
||||
</span>
|
||||
<div class="flex flex-col items-stretch gap-4">
|
||||
<HoppButtonPrimary
|
||||
:icon="IconImport"
|
||||
:label="t('import.title')"
|
||||
filled
|
||||
outline
|
||||
@click="emit('display-modal-import-export')"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
:icon="IconPlus"
|
||||
:label="t('add.new')"
|
||||
filled
|
||||
outline
|
||||
@click="emit('display-modal-add')"
|
||||
/>
|
||||
<template #body>
|
||||
<div class="flex flex-col items-center space-y-4">
|
||||
<span class="text-center text-secondaryLight">
|
||||
{{ t("collection.import_or_create") }}
|
||||
</span>
|
||||
<div class="flex flex-col items-stretch gap-4">
|
||||
<HoppButtonPrimary
|
||||
:icon="IconImport"
|
||||
:label="t('import.title')"
|
||||
filled
|
||||
outline
|
||||
@click="emit('display-modal-import-export')"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
:icon="IconPlus"
|
||||
:label="t('add.new')"
|
||||
filled
|
||||
outline
|
||||
@click="emit('display-modal-add')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
<HoppSmartPlaceholder
|
||||
v-else-if="node.data.type === 'collections'"
|
||||
@@ -291,18 +293,20 @@
|
||||
:alt="`${t('empty.collections')}`"
|
||||
:text="t('empty.collections')"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
:label="t('add.new')"
|
||||
filled
|
||||
outline
|
||||
@click="
|
||||
node.data.type === 'collections' &&
|
||||
emit('add-folder', {
|
||||
path: node.id,
|
||||
folder: node.data.data.data,
|
||||
})
|
||||
"
|
||||
/>
|
||||
<template #body>
|
||||
<HoppButtonSecondary
|
||||
:label="t('add.new')"
|
||||
filled
|
||||
outline
|
||||
@click="
|
||||
node.data.type === 'collections' &&
|
||||
emit('add-folder', {
|
||||
path: node.id,
|
||||
folder: node.data.data.data,
|
||||
})
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
<HoppSmartPlaceholder
|
||||
v-else-if="node.data.type === 'folders'"
|
||||
|
||||
@@ -274,33 +274,37 @@
|
||||
:text="t('empty.collections')"
|
||||
@drop.stop
|
||||
>
|
||||
<div class="flex flex-col items-center space-y-4">
|
||||
<span class="text-center text-secondaryLight">
|
||||
{{ t("collection.import_or_create") }}
|
||||
</span>
|
||||
<div class="flex flex-col items-stretch gap-4">
|
||||
<HoppButtonPrimary
|
||||
:icon="IconImport"
|
||||
:label="t('import.title')"
|
||||
filled
|
||||
outline
|
||||
:disabled="hasNoTeamAccess"
|
||||
:title="hasNoTeamAccess ? t('team.no_access') : ''"
|
||||
@click="
|
||||
hasNoTeamAccess ? null : emit('display-modal-import-export')
|
||||
"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
:icon="IconPlus"
|
||||
:label="t('add.new')"
|
||||
filled
|
||||
outline
|
||||
:disabled="hasNoTeamAccess"
|
||||
:title="hasNoTeamAccess ? t('team.no_access') : ''"
|
||||
@click="hasNoTeamAccess ? null : emit('display-modal-add')"
|
||||
/>
|
||||
<template #body>
|
||||
<div class="flex flex-col items-center space-y-4">
|
||||
<span class="text-center text-secondaryLight">
|
||||
{{ t("collection.import_or_create") }}
|
||||
</span>
|
||||
<div class="flex flex-col items-stretch gap-4">
|
||||
<HoppButtonPrimary
|
||||
:icon="IconImport"
|
||||
:label="t('import.title')"
|
||||
filled
|
||||
outline
|
||||
:disabled="hasNoTeamAccess"
|
||||
:title="hasNoTeamAccess ? t('team.no_access') : ''"
|
||||
@click="
|
||||
hasNoTeamAccess
|
||||
? null
|
||||
: emit('display-modal-import-export')
|
||||
"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
:icon="IconPlus"
|
||||
:label="t('add.new')"
|
||||
filled
|
||||
outline
|
||||
:disabled="hasNoTeamAccess"
|
||||
:title="hasNoTeamAccess ? t('team.no_access') : ''"
|
||||
@click="hasNoTeamAccess ? null : emit('display-modal-add')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
<HoppSmartPlaceholder
|
||||
v-else-if="node.data.type === 'collections'"
|
||||
@@ -309,18 +313,20 @@
|
||||
:text="t('empty.collections')"
|
||||
@drop.stop
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
:label="t('add.new')"
|
||||
filled
|
||||
outline
|
||||
@click="
|
||||
node.data.type === 'collections' &&
|
||||
emit('add-folder', {
|
||||
path: node.id,
|
||||
folder: node.data.data.data,
|
||||
})
|
||||
"
|
||||
/>
|
||||
<template #body>
|
||||
<HoppButtonSecondary
|
||||
:label="t('add.new')"
|
||||
filled
|
||||
outline
|
||||
@click="
|
||||
node.data.type === 'collections' &&
|
||||
emit('add-folder', {
|
||||
path: node.id,
|
||||
folder: node.data.data.data,
|
||||
})
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
<HoppSmartPlaceholder
|
||||
v-else-if="node.data.type === 'folders'"
|
||||
|
||||
@@ -180,16 +180,18 @@
|
||||
:alt="`${t('empty.collection')}`"
|
||||
:text="t('empty.collection')"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
:label="t('add.new')"
|
||||
filled
|
||||
outline
|
||||
@click="
|
||||
emit('add-folder', {
|
||||
path: `${collectionIndex}`,
|
||||
})
|
||||
"
|
||||
/>
|
||||
<template #body>
|
||||
<HoppButtonSecondary
|
||||
:label="t('add.new')"
|
||||
filled
|
||||
outline
|
||||
@click="
|
||||
emit('add-folder', {
|
||||
path: `${collectionIndex}`,
|
||||
})
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -176,8 +176,7 @@
|
||||
:src="`/images/states/${colorMode.value}/pack.svg`"
|
||||
:alt="`${t('empty.folder')}`"
|
||||
:text="t('empty.folder')"
|
||||
>
|
||||
</HoppSmartPlaceholder>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<HoppSmartConfirmModal
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
type="search"
|
||||
autocomplete="off"
|
||||
:placeholder="t('action.search')"
|
||||
class="!border-0 bg-transparent py-2 pl-4 pr-2"
|
||||
class="flex w-full bg-transparent px-4 py-2 h-8"
|
||||
/>
|
||||
<div
|
||||
class="flex flex-1 flex-shrink-0 justify-between border-y border-dividerLight bg-primary"
|
||||
@@ -66,34 +66,36 @@
|
||||
:alt="`${t('empty.collections')}`"
|
||||
:text="t('empty.collections')"
|
||||
>
|
||||
<div class="flex flex-col items-center space-y-4">
|
||||
<span class="text-center text-secondaryLight">
|
||||
{{ t("collection.import_or_create") }}
|
||||
</span>
|
||||
<div class="flex flex-col items-stretch gap-4">
|
||||
<HoppButtonPrimary
|
||||
:icon="IconImport"
|
||||
:label="t('import.title')"
|
||||
filled
|
||||
outline
|
||||
@click="displayModalImportExport(true)"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
:label="t('add.new')"
|
||||
filled
|
||||
outline
|
||||
:icon="IconPlus"
|
||||
@click="displayModalAdd(true)"
|
||||
/>
|
||||
<template #body>
|
||||
<div class="flex flex-col items-center space-y-4">
|
||||
<span class="text-center text-secondaryLight">
|
||||
{{ t("collection.import_or_create") }}
|
||||
</span>
|
||||
<div class="flex flex-col items-stretch gap-4">
|
||||
<HoppButtonPrimary
|
||||
:icon="IconImport"
|
||||
:label="t('import.title')"
|
||||
filled
|
||||
outline
|
||||
@click="displayModalImportExport(true)"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
:label="t('add.new')"
|
||||
filled
|
||||
outline
|
||||
:icon="IconPlus"
|
||||
@click="displayModalAdd(true)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
<HoppSmartPlaceholder
|
||||
v-if="!(filteredCollections.length !== 0 || collections.length === 0)"
|
||||
:text="`${t('state.nothing_found')} ‟${filterText}”`"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-lucide-search class="svg-icons pb-2 opacity-75" />
|
||||
<icon-lucide-search class="svg-icons opacity-75" />
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
<CollectionsGraphqlAdd
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
v-model="filterTexts"
|
||||
type="search"
|
||||
autocomplete="off"
|
||||
class="flex w-full bg-transparent px-4 py-2"
|
||||
class="flex w-full bg-transparent px-4 py-2 h-8"
|
||||
:placeholder="t('action.search')"
|
||||
:disabled="collectionsType.type === 'team-collections'"
|
||||
/>
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
v-if="!currentInterceptorSupportsCookies"
|
||||
:text="t('cookies.modal.interceptor_no_support')"
|
||||
>
|
||||
<AppInterceptor class="rounded border border-dividerLight p-2" />
|
||||
<template #body>
|
||||
<AppInterceptor class="rounded border border-dividerLight p-2" />
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
<div v-else class="flex flex-col">
|
||||
<div
|
||||
@@ -38,8 +40,7 @@
|
||||
:alt="`${t('cookies.modal.empty_domains')}`"
|
||||
:text="t('cookies.modal.empty_domains')"
|
||||
class="mt-6"
|
||||
>
|
||||
</HoppSmartPlaceholder>
|
||||
/>
|
||||
<div
|
||||
v-for="[domain, entries] in workingCookieJar.entries()"
|
||||
v-else
|
||||
|
||||
208
packages/hoppscotch-common/src/components/embeds/index.vue
Normal file
208
packages/hoppscotch-common/src/components/embeds/index.vue
Normal file
@@ -0,0 +1,208 @@
|
||||
<template>
|
||||
<div class="flex flex-col flex-1">
|
||||
<header
|
||||
class="flex items-center justify-between flex-1 flex-shrink-0 px-2 py-2 space-x-2 overflow-x-auto overflow-y-hidden"
|
||||
>
|
||||
<div class="flex items-center justify-between flex-1 space-x-2">
|
||||
<HoppButtonSecondary
|
||||
class="!font-bold uppercase tracking-wide !text-secondaryDark hover:bg-primaryDark focus-visible:bg-primaryDark"
|
||||
:label="t('app.name')"
|
||||
to="https://hoppscotch.io"
|
||||
blank
|
||||
/>
|
||||
<div class="flex">
|
||||
<HoppButtonSecondary
|
||||
:label="t('app.open_in_hoppscotch')"
|
||||
:to="sharedRequestURL"
|
||||
blank
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="sticky top-0 z-10 flex-1">
|
||||
<div
|
||||
class="flex-none flex-shrink-0 p-4 bg-primary sm:flex sm:flex-shrink-0 sm:space-x-2"
|
||||
>
|
||||
<div
|
||||
class="flex flex-1 overflow-hidden border divide-x rounded text-secondaryDark divide-divider min-w-[12rem] overflow-x-auto border-divider"
|
||||
>
|
||||
<span
|
||||
class="flex items-center justify-center px-4 py-2 font-semibold transition rounded-l"
|
||||
>
|
||||
{{ tab.document.request.method }}
|
||||
</span>
|
||||
<div
|
||||
class="flex items-center flex-1 flex-shrink-0 min-w-0 px-4 py-2 truncate rounded-r"
|
||||
>
|
||||
{{ tab.document.request.endpoint }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex mt-2 space-x-2 sm:mt-0">
|
||||
<HoppButtonPrimary
|
||||
id="send"
|
||||
:title="`${t(
|
||||
'action.send'
|
||||
)} <kbd>${getSpecialKey()}</kbd><kbd>↩</kbd>`"
|
||||
:label="`${!loading ? t('action.send') : t('action.cancel')}`"
|
||||
class="flex-1 min-w-20"
|
||||
outline
|
||||
@click="!loading ? newSendRequest() : cancelRequest()"
|
||||
/>
|
||||
<div class="flex">
|
||||
<HoppButtonSecondary
|
||||
:title="`${t(
|
||||
'request.save'
|
||||
)} <kbd>${getSpecialKey()}</kbd><kbd>S</kbd>`"
|
||||
:label="t('request.save')"
|
||||
filled
|
||||
:icon="IconSave"
|
||||
class="flex-1 rounded"
|
||||
blank
|
||||
outline
|
||||
:to="sharedRequestURL"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<HttpRequestOptions
|
||||
v-model="tab.document.request"
|
||||
v-model:option-tab="selectedOptionTab"
|
||||
:properties="properties"
|
||||
/>
|
||||
<HttpResponse :document="tab.document" :is-embed="true" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Ref } from "vue"
|
||||
import { computed, useModel } from "vue"
|
||||
import { ref } from "vue"
|
||||
import { useI18n } from "~/composables/i18n"
|
||||
import { useToast } from "~/composables/toast"
|
||||
import { getPlatformSpecialKey as getSpecialKey } from "~/helpers/platformutils"
|
||||
import * as E from "fp-ts/Either"
|
||||
import { useStreamSubscriber } from "~/composables/stream"
|
||||
import { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
|
||||
import { runRESTRequest$ } from "~/helpers/RequestRunner"
|
||||
import { HoppTab } from "~/services/tab"
|
||||
import { HoppRESTDocument } from "~/helpers/rest/document"
|
||||
import IconSave from "~icons/lucide/save"
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
|
||||
const props = defineProps<{
|
||||
modelTab: HoppTab<HoppRESTDocument>
|
||||
properties: string[]
|
||||
sharedRequestID: string
|
||||
}>()
|
||||
|
||||
const tab = useModel(props, "modelTab")
|
||||
|
||||
const selectedOptionTab = ref(props.properties[0])
|
||||
|
||||
const requestCancelFunc: Ref<(() => void) | null> = ref(null)
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const shortcodeBaseURL =
|
||||
import.meta.env.VITE_SHORTCODE_BASE_URL ?? "https://hopp.sh"
|
||||
|
||||
const sharedRequestURL = computed(() => {
|
||||
return `${shortcodeBaseURL}/r/${props.sharedRequestID}`
|
||||
})
|
||||
|
||||
const { subscribeToStream } = useStreamSubscriber()
|
||||
|
||||
const newSendRequest = async () => {
|
||||
if (newEndpoint.value === "" || /^\s+$/.test(newEndpoint.value)) {
|
||||
toast.error(`${t("empty.endpoint")}`)
|
||||
return
|
||||
}
|
||||
|
||||
ensureMethodInEndpoint()
|
||||
|
||||
loading.value = true
|
||||
|
||||
const [cancel, streamPromise] = runRESTRequest$(tab)
|
||||
const streamResult = await streamPromise
|
||||
|
||||
requestCancelFunc.value = cancel
|
||||
if (E.isRight(streamResult)) {
|
||||
subscribeToStream(
|
||||
streamResult.right,
|
||||
(responseState) => {
|
||||
if (loading.value) {
|
||||
// Check exists because, loading can be set to false
|
||||
// when cancelled
|
||||
updateRESTResponse(responseState)
|
||||
}
|
||||
},
|
||||
() => {
|
||||
loading.value = false
|
||||
},
|
||||
() => {
|
||||
// TODO: Change this any to a proper type
|
||||
const result = (streamResult.right as any).value
|
||||
if (
|
||||
result.type === "network_fail" &&
|
||||
result.error?.error === "NO_PW_EXT_HOOK"
|
||||
) {
|
||||
const errorResponse: HoppRESTResponse = {
|
||||
type: "extension_error",
|
||||
error: result.error.humanMessage.heading,
|
||||
component: result.error.component,
|
||||
req: result.req,
|
||||
}
|
||||
updateRESTResponse(errorResponse)
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
)
|
||||
} else {
|
||||
loading.value = false
|
||||
toast.error(`${t("error.script_fail")}`)
|
||||
let error: Error
|
||||
if (typeof streamResult.left === "string") {
|
||||
error = { name: "RequestFailure", message: streamResult.left }
|
||||
} else {
|
||||
error = streamResult.left
|
||||
}
|
||||
updateRESTResponse({
|
||||
type: "script_fail",
|
||||
error,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const updateRESTResponse = (response: HoppRESTResponse | null) => {
|
||||
tab.value.document.response = response
|
||||
}
|
||||
|
||||
const newEndpoint = computed(() => {
|
||||
return tab.value.document.request.endpoint
|
||||
})
|
||||
|
||||
const ensureMethodInEndpoint = () => {
|
||||
if (
|
||||
!/^http[s]?:\/\//.test(newEndpoint.value) &&
|
||||
!newEndpoint.value.startsWith("<<")
|
||||
) {
|
||||
const domain = newEndpoint.value.split(/[/:#?]+/)[0]
|
||||
if (domain === "localhost" || /([0-9]+\.)*[0-9]/.test(domain)) {
|
||||
tab.value.document.request.endpoint =
|
||||
"http://" + tab.value.document.request.endpoint
|
||||
} else {
|
||||
tab.value.document.request.endpoint =
|
||||
"https://" + tab.value.document.request.endpoint
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const cancelRequest = () => {
|
||||
loading.value = false
|
||||
requestCancelFunc.value?.()
|
||||
|
||||
updateRESTResponse(null)
|
||||
}
|
||||
</script>
|
||||
@@ -93,20 +93,12 @@
|
||||
}
|
||||
"
|
||||
/>
|
||||
<div
|
||||
<HoppSmartPlaceholder
|
||||
v-if="myEnvironments.length === 0"
|
||||
class="flex flex-col items-center justify-center text-secondaryLight"
|
||||
>
|
||||
<img
|
||||
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
||||
loading="lazy"
|
||||
class="mb-2 inline-flex h-16 w-16 flex-col object-contain object-center"
|
||||
:alt="`${t('empty.environments')}`"
|
||||
/>
|
||||
<span class="pb-2 text-center">
|
||||
{{ t("empty.environments") }}
|
||||
</span>
|
||||
</div>
|
||||
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
||||
:alt="`${t('empty.environments')}`"
|
||||
:text="t('empty.environments')"
|
||||
/>
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab
|
||||
:id="'team-environments'"
|
||||
@@ -140,20 +132,12 @@
|
||||
}
|
||||
"
|
||||
/>
|
||||
<div
|
||||
<HoppSmartPlaceholder
|
||||
v-if="teamEnvironmentList.length === 0"
|
||||
class="flex flex-col items-center justify-center text-secondaryLight"
|
||||
>
|
||||
<img
|
||||
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
||||
loading="lazy"
|
||||
class="mb-2 inline-flex h-16 w-16 flex-col object-contain object-center"
|
||||
:alt="`${t('empty.environments')}`"
|
||||
/>
|
||||
<span class="pb-2 text-center">
|
||||
{{ t("empty.environments") }}
|
||||
</span>
|
||||
</div>
|
||||
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
||||
:alt="`${t('empty.environments')}`"
|
||||
:text="t('empty.environments')"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="!teamListLoading && teamAdapterError"
|
||||
|
||||
@@ -78,11 +78,13 @@
|
||||
:alt="`${t('empty.environments')}`"
|
||||
:text="t('empty.environments')"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
:label="`${t('add.new')}`"
|
||||
filled
|
||||
@click="addEnvironmentVariable"
|
||||
/>
|
||||
<template #body>
|
||||
<HoppButtonSecondary
|
||||
:label="`${t('add.new')}`"
|
||||
filled
|
||||
@click="addEnvironmentVariable"
|
||||
/>
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -38,27 +38,29 @@
|
||||
:alt="`${t('empty.environments')}`"
|
||||
:text="t('empty.environments')"
|
||||
>
|
||||
<div class="flex flex-col items-center space-y-4">
|
||||
<span class="text-center text-secondaryLight">
|
||||
{{ t("environment.import_or_create") }}
|
||||
</span>
|
||||
<div class="flex flex-col items-stretch gap-4">
|
||||
<HoppButtonPrimary
|
||||
:icon="IconImport"
|
||||
:label="t('import.title')"
|
||||
filled
|
||||
outline
|
||||
@click="displayModalImportExport(true)"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
:icon="IconPlus"
|
||||
:label="`${t('add.new')}`"
|
||||
filled
|
||||
outline
|
||||
@click="displayModalAdd(true)"
|
||||
/>
|
||||
<template #body>
|
||||
<div class="flex flex-col items-center space-y-4">
|
||||
<span class="text-center text-secondaryLight">
|
||||
{{ t("environment.import_or_create") }}
|
||||
</span>
|
||||
<div class="flex flex-col items-stretch gap-4">
|
||||
<HoppButtonPrimary
|
||||
:icon="IconImport"
|
||||
:label="t('import.title')"
|
||||
filled
|
||||
outline
|
||||
@click="displayModalImportExport(true)"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
:icon="IconPlus"
|
||||
:label="`${t('add.new')}`"
|
||||
filled
|
||||
outline
|
||||
@click="displayModalAdd(true)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
<EnvironmentsMyDetails
|
||||
:show="showModalDetails"
|
||||
|
||||
@@ -81,18 +81,20 @@
|
||||
:alt="`${t('empty.environments')}`"
|
||||
:text="t('empty.environments')"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
v-if="isViewer"
|
||||
disabled
|
||||
:label="`${t('add.new')}`"
|
||||
filled
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-else
|
||||
:label="`${t('add.new')}`"
|
||||
filled
|
||||
@click="addEnvironmentVariable"
|
||||
/>
|
||||
<template #body>
|
||||
<HoppButtonSecondary
|
||||
v-if="isViewer"
|
||||
disabled
|
||||
:label="`${t('add.new')}`"
|
||||
filled
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-else
|
||||
:label="`${t('add.new')}`"
|
||||
filled
|
||||
@click="addEnvironmentVariable"
|
||||
/>
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -49,31 +49,33 @@
|
||||
:alt="`${t('empty.environments')}`"
|
||||
:text="t('empty.environments')"
|
||||
>
|
||||
<div class="flex flex-col items-center space-y-4">
|
||||
<span class="text-center text-secondaryLight">
|
||||
{{ t("environment.import_or_create") }}
|
||||
</span>
|
||||
<div class="flex flex-col items-stretch gap-4">
|
||||
<HoppButtonPrimary
|
||||
:icon="IconImport"
|
||||
:label="t('import.title')"
|
||||
filled
|
||||
outline
|
||||
:title="isTeamViewer ? t('team.no_access') : ''"
|
||||
:disabled="isTeamViewer"
|
||||
@click="isTeamViewer ? null : displayModalImportExport(true)"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
:label="`${t('add.new')}`"
|
||||
filled
|
||||
outline
|
||||
:icon="IconPlus"
|
||||
:title="isTeamViewer ? t('team.no_access') : ''"
|
||||
:disabled="isTeamViewer"
|
||||
@click="isTeamViewer ? null : displayModalAdd(true)"
|
||||
/>
|
||||
<template #body>
|
||||
<div class="flex flex-col items-center space-y-4">
|
||||
<span class="text-center text-secondaryLight">
|
||||
{{ t("environment.import_or_create") }}
|
||||
</span>
|
||||
<div class="flex flex-col items-stretch gap-4">
|
||||
<HoppButtonPrimary
|
||||
:icon="IconImport"
|
||||
:label="t('import.title')"
|
||||
filled
|
||||
outline
|
||||
:title="isTeamViewer ? t('team.no_access') : ''"
|
||||
:disabled="isTeamViewer"
|
||||
@click="isTeamViewer ? null : displayModalImportExport(true)"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
:label="`${t('add.new')}`"
|
||||
filled
|
||||
outline
|
||||
:icon="IconPlus"
|
||||
:title="isTeamViewer ? t('team.no_access') : ''"
|
||||
:disabled="isTeamViewer"
|
||||
@click="isTeamViewer ? null : displayModalAdd(true)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
<div v-else-if="!loading">
|
||||
<EnvironmentsTeamsEnvironment
|
||||
|
||||
@@ -120,14 +120,16 @@
|
||||
:alt="`${t('empty.authorization')}`"
|
||||
:text="t('empty.authorization')"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
outline
|
||||
:label="t('app.documentation')"
|
||||
to="https://docs.hoppscotch.io/documentation/features/authorization"
|
||||
blank
|
||||
:icon="IconExternalLink"
|
||||
reverse
|
||||
/>
|
||||
<template #body>
|
||||
<HoppButtonSecondary
|
||||
outline
|
||||
:label="t('app.documentation')"
|
||||
to="https://docs.hoppscotch.io/documentation/features/authorization"
|
||||
blank
|
||||
:icon="IconExternalLink"
|
||||
reverse
|
||||
/>
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
<div v-else class="flex flex-1 border-b border-dividerLight">
|
||||
<div class="w-2/3 border-r border-dividerLight">
|
||||
|
||||
@@ -162,12 +162,14 @@
|
||||
:alt="`${t('empty.headers')}`"
|
||||
:text="t('empty.headers')"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
:label="`${t('add.new')}`"
|
||||
filled
|
||||
:icon="IconPlus"
|
||||
@click="addHeader"
|
||||
/>
|
||||
<template #body>
|
||||
<HoppButtonSecondary
|
||||
:label="`${t('add.new')}`"
|
||||
filled
|
||||
:icon="IconPlus"
|
||||
@click="addHeader"
|
||||
/>
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -20,8 +20,7 @@
|
||||
:src="`/images/states/${colorMode.value}/add_comment.svg`"
|
||||
:alt="`${t('empty.documentation')}`"
|
||||
:text="t('empty.documentation')"
|
||||
>
|
||||
</HoppSmartPlaceholder>
|
||||
/>
|
||||
<div v-else>
|
||||
<div
|
||||
class="sticky top-0 z-10 flex flex-shrink-0 overflow-x-auto bg-primary"
|
||||
@@ -30,7 +29,7 @@
|
||||
v-model="graphqlFieldsFilterText"
|
||||
type="search"
|
||||
autocomplete="off"
|
||||
class="flex w-full bg-transparent px-4 py-2"
|
||||
class="flex w-full bg-transparent px-4 py-2 h-8"
|
||||
:placeholder="`${t('action.search')}`"
|
||||
/>
|
||||
<div class="flex">
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
<div
|
||||
class="sticky top-0 z-10 flex flex-shrink-0 flex-col overflow-x-auto border-b border-dividerLight bg-primary"
|
||||
>
|
||||
<WorkspaceCurrent :section="t('tab.history')" />
|
||||
<WorkspaceCurrent :section="t('tab.history')" :is-only-personal="true" />
|
||||
<div class="flex">
|
||||
<input
|
||||
v-model="filterText"
|
||||
type="search"
|
||||
autocomplete="off"
|
||||
class="flex w-full bg-transparent px-4 py-2"
|
||||
class="flex w-full bg-transparent px-4 py-2 h-8"
|
||||
:placeholder="`${t('action.search')}`"
|
||||
/>
|
||||
<div class="flex">
|
||||
@@ -114,8 +114,7 @@
|
||||
:src="`/images/states/${colorMode.value}/history.svg`"
|
||||
:alt="`${t('empty.history')}`"
|
||||
:text="t('empty.history')"
|
||||
>
|
||||
</HoppSmartPlaceholder>
|
||||
/>
|
||||
<HoppSmartPlaceholder
|
||||
v-else-if="
|
||||
Object.keys(filteredHistoryGroups).length === 0 ||
|
||||
@@ -124,18 +123,20 @@
|
||||
:text="`${t('state.nothing_found')} ‟${filterText || filterSelection}”`"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-lucide-search class="svg-icons pb-2 opacity-75" />
|
||||
<icon-lucide-search class="svg-icons opacity-75" />
|
||||
</template>
|
||||
<template #body>
|
||||
<HoppButtonSecondary
|
||||
:label="t('action.clear')"
|
||||
outline
|
||||
@click="
|
||||
() => {
|
||||
filterText = ''
|
||||
filterSelection = 'ALL'
|
||||
}
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
<HoppButtonSecondary
|
||||
:label="t('action.clear')"
|
||||
outline
|
||||
@click="
|
||||
() => {
|
||||
filterText = ''
|
||||
filterSelection = 'ALL'
|
||||
}
|
||||
"
|
||||
/>
|
||||
</HoppSmartPlaceholder>
|
||||
<HoppSmartConfirmModal
|
||||
:show="confirmRemove"
|
||||
|
||||
@@ -119,14 +119,16 @@
|
||||
:alt="`${t('empty.authorization')}`"
|
||||
:text="t('empty.authorization')"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
outline
|
||||
:label="t('app.documentation')"
|
||||
to="https://docs.hoppscotch.io/documentation/features/authorization"
|
||||
blank
|
||||
:icon="IconExternalLink"
|
||||
reverse
|
||||
/>
|
||||
<template #body>
|
||||
<HoppButtonSecondary
|
||||
outline
|
||||
:label="t('app.documentation')"
|
||||
to="https://docs.hoppscotch.io/documentation/features/authorization"
|
||||
blank
|
||||
:icon="IconExternalLink"
|
||||
reverse
|
||||
/>
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
<div v-else class="flex flex-1 border-b border-dividerLight">
|
||||
<div class="w-2/3 border-r border-dividerLight">
|
||||
|
||||
@@ -112,14 +112,16 @@
|
||||
:alt="`${t('empty.body')}`"
|
||||
:text="t('empty.body')"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
outline
|
||||
:label="`${t('app.documentation')}`"
|
||||
to="https://docs.hoppscotch.io/documentation/getting-started/rest/uploading-data"
|
||||
blank
|
||||
:icon="IconExternalLink"
|
||||
reverse
|
||||
/>
|
||||
<template #body>
|
||||
<HoppButtonSecondary
|
||||
outline
|
||||
:label="`${t('app.documentation')}`"
|
||||
to="https://docs.hoppscotch.io/documentation/getting-started/rest/uploading-data"
|
||||
blank
|
||||
:icon="IconExternalLink"
|
||||
reverse
|
||||
/>
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -158,12 +158,14 @@
|
||||
:alt="`${t('empty.body')}`"
|
||||
:text="t('empty.body')"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
:label="`${t('add.new')}`"
|
||||
filled
|
||||
:icon="IconPlus"
|
||||
@click="addBodyParam"
|
||||
/>
|
||||
<template #body>
|
||||
<HoppButtonSecondary
|
||||
:label="`${t('add.new')}`"
|
||||
filled
|
||||
:icon="IconPlus"
|
||||
@click="addBodyParam"
|
||||
/>
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
:text="`${t('state.nothing_found')} ‟${searchQuery}”`"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-lucide-search class="svg-icons pb-2 opacity-75" />
|
||||
<icon-lucide-search class="svg-icons opacity-75" />
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
</div>
|
||||
|
||||
@@ -209,12 +209,14 @@
|
||||
:alt="`${t('empty.headers')}`"
|
||||
:text="t('empty.headers')"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
filled
|
||||
:label="`${t('add.new')}`"
|
||||
:icon="IconPlus"
|
||||
@click="addHeader"
|
||||
/>
|
||||
<template #body>
|
||||
<HoppButtonSecondary
|
||||
filled
|
||||
:label="`${t('add.new')}`"
|
||||
:icon="IconPlus"
|
||||
@click="addHeader"
|
||||
/>
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -157,12 +157,14 @@
|
||||
:alt="`${t('empty.parameters')}`"
|
||||
:text="t('empty.parameters')"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
:label="`${t('add.new')}`"
|
||||
:icon="IconPlus"
|
||||
filled
|
||||
@click="addParam"
|
||||
/>
|
||||
<template #body>
|
||||
<HoppButtonSecondary
|
||||
:label="`${t('add.new')}`"
|
||||
:icon="IconPlus"
|
||||
filled
|
||||
@click="addParam"
|
||||
/>
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -180,6 +180,18 @@
|
||||
class="input mb-2 !bg-primaryContrast"
|
||||
@keyup.enter="hide()"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
ref="saveRequestAction"
|
||||
:label="`${t('request.save_as')}`"
|
||||
:icon="IconFolderPlus"
|
||||
@click="
|
||||
() => {
|
||||
showSaveRequestModal = true
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<hr />
|
||||
<HoppSmartItem
|
||||
ref="copyRequestAction"
|
||||
:label="t('request.share_request')"
|
||||
@@ -192,18 +204,6 @@
|
||||
}
|
||||
"
|
||||
/>
|
||||
<hr />
|
||||
<HoppSmartItem
|
||||
ref="saveRequestAction"
|
||||
:label="`${t('request.save_as')}`"
|
||||
:icon="IconFolderPlus"
|
||||
@click="
|
||||
() => {
|
||||
showSaveRequestModal = true
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</tippy>
|
||||
|
||||
@@ -5,13 +5,18 @@
|
||||
render-inactive-tabs
|
||||
>
|
||||
<HoppSmartTab
|
||||
v-if="properties ? properties.includes('parameters') : true"
|
||||
:id="'params'"
|
||||
:label="`${t('tab.parameters')}`"
|
||||
:info="`${newActiveParamsCount$}`"
|
||||
>
|
||||
<HttpParameters v-model="request.params" />
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab :id="'bodyParams'" :label="`${t('tab.body')}`">
|
||||
<HoppSmartTab
|
||||
v-if="properties ? properties.includes('body') : true"
|
||||
:id="'bodyParams'"
|
||||
:label="`${t('tab.body')}`"
|
||||
>
|
||||
<HttpBody
|
||||
v-model:headers="request.headers"
|
||||
v-model:body="request.body"
|
||||
@@ -19,16 +24,22 @@
|
||||
/>
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab
|
||||
v-if="properties ? properties.includes('headers') : true"
|
||||
:id="'headers'"
|
||||
:label="`${t('tab.headers')}`"
|
||||
:info="`${newActiveHeadersCount$}`"
|
||||
>
|
||||
<HttpHeaders v-model="request" @change-tab="changeOptionTab" />
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab :id="'authorization'" :label="`${t('tab.authorization')}`">
|
||||
<HoppSmartTab
|
||||
v-if="properties ? properties.includes('authorization') : true"
|
||||
:id="'authorization'"
|
||||
:label="`${t('tab.authorization')}`"
|
||||
>
|
||||
<HttpAuthorization v-model="request.auth" />
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab
|
||||
v-if="properties ? properties.includes('preRequestScript') : true"
|
||||
:id="'preRequestScript'"
|
||||
:label="`${t('tab.pre_request_script')}`"
|
||||
:indicator="
|
||||
@@ -40,6 +51,7 @@
|
||||
<HttpPreRequestScript v-model="request.preRequestScript" />
|
||||
</HoppSmartTab>
|
||||
<HoppSmartTab
|
||||
v-if="properties ? properties.includes('tests') : true"
|
||||
:id="'tests'"
|
||||
:label="`${t('tab.tests')}`"
|
||||
:indicator="
|
||||
@@ -76,6 +88,7 @@ const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue: HoppRESTRequest
|
||||
optionTab: RESTOptionTabs
|
||||
properties?: string[]
|
||||
}>(),
|
||||
{
|
||||
optionTab: "params",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="relative flex flex-1 flex-col">
|
||||
<HttpResponseMeta :response="doc.response" />
|
||||
<HttpResponseMeta :response="doc.response" :is-embed="isEmbed" />
|
||||
<LensesResponseBodyRenderer
|
||||
v-if="!loading && hasResponse"
|
||||
v-model:document="doc"
|
||||
@@ -15,6 +15,7 @@ import { HoppRESTDocument } from "~/helpers/rest/document"
|
||||
|
||||
const props = defineProps<{
|
||||
document: HoppRESTDocument
|
||||
isEmbed: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -2,8 +2,20 @@
|
||||
<div
|
||||
class="sticky top-0 z-10 flex flex-shrink-0 items-center justify-center overflow-auto overflow-x-auto whitespace-nowrap bg-primary p-4"
|
||||
>
|
||||
<AppShortcutsPrompt v-if="response == null" class="flex-1" />
|
||||
<div v-else class="flex flex-1 flex-col">
|
||||
<AppShortcutsPrompt v-if="response == null && !isEmbed" class="flex-1" />
|
||||
|
||||
<div v-if="response == null && isEmbed">
|
||||
<HoppButtonSecondary
|
||||
:label="`${t('app.documentation')}`"
|
||||
to="https://docs.hoppscotch.io/documentation/features/rest-api-testing#response"
|
||||
:icon="IconExternalLink"
|
||||
blank
|
||||
outline
|
||||
reverse
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-else-if="response" class="flex flex-1 flex-col">
|
||||
<div
|
||||
v-if="response.type === 'loading'"
|
||||
class="flex flex-col items-center justify-center"
|
||||
@@ -25,7 +37,9 @@
|
||||
:text="t('helpers.network_fail')"
|
||||
large
|
||||
>
|
||||
<AppInterceptor class="rounded border border-dividerLight p-2" />
|
||||
<template #body>
|
||||
<AppInterceptor class="rounded border border-dividerLight p-2" />
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
<HoppSmartPlaceholder
|
||||
v-if="response.type === 'script_fail'"
|
||||
@@ -35,12 +49,14 @@
|
||||
:text="t('helpers.script_fail')"
|
||||
large
|
||||
>
|
||||
<div
|
||||
class="mt-2 w-full overflow-auto whitespace-normal rounded bg-primaryLight px-4 py-2 font-mono text-red-400"
|
||||
>
|
||||
{{ response.error.name }}: {{ response.error.message }}<br />
|
||||
{{ response.error.stack }}
|
||||
</div>
|
||||
<template #body>
|
||||
<div
|
||||
class="mt-2 w-full overflow-auto whitespace-normal rounded bg-primaryLight px-4 py-2 font-mono text-red-400"
|
||||
>
|
||||
{{ response.error.name }}: {{ response.error.message }}<br />
|
||||
{{ response.error.stack }}
|
||||
</div>
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
<div
|
||||
v-if="response.type === 'success' || response.type === 'fail'"
|
||||
@@ -105,6 +121,7 @@ import { getStatusCodeReasonPhrase } from "~/helpers/utils/statusCodes"
|
||||
import { useService } from "dioc/vue"
|
||||
import { InspectionService } from "~/services/inspection"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
import IconExternalLink from "~icons/lucide/external-link"
|
||||
|
||||
const t = useI18n()
|
||||
const colorMode = useColorMode()
|
||||
@@ -112,6 +129,7 @@ const tabs = useService(RESTTabService)
|
||||
|
||||
const props = defineProps<{
|
||||
response: HoppRESTResponse | null | undefined
|
||||
isEmbed?: boolean
|
||||
}>()
|
||||
|
||||
/**
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
<div class="divide-y divide-dividerLight">
|
||||
<div
|
||||
v-if="noEnvSelected && !globalHasAdditions"
|
||||
class="flex bg-info p-4 text-secondaryDark"
|
||||
class="flex bg-bannerInfo p-4 text-secondaryDark"
|
||||
role="alert"
|
||||
>
|
||||
<icon-lucide-alert-triangle class="svg-icons mr-4" />
|
||||
@@ -159,8 +159,7 @@
|
||||
:alt="`${t('error.test_script_fail')}`"
|
||||
:heading="t('error.test_script_fail')"
|
||||
:text="t('helpers.test_script_fail')"
|
||||
>
|
||||
</HoppSmartPlaceholder>
|
||||
/>
|
||||
<HoppSmartPlaceholder
|
||||
v-else
|
||||
:src="`/images/states/${colorMode.value}/validation.svg`"
|
||||
@@ -168,15 +167,16 @@
|
||||
:heading="t('empty.tests')"
|
||||
:text="t('helpers.tests')"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
outline
|
||||
:label="`${t('action.learn_more')}`"
|
||||
to="https://docs.hoppscotch.io/documentation/getting-started/rest/tests"
|
||||
blank
|
||||
:icon="IconExternalLink"
|
||||
reverse
|
||||
class="my-4"
|
||||
/>
|
||||
<template #body>
|
||||
<HoppButtonSecondary
|
||||
outline
|
||||
:label="`${t('action.learn_more')}`"
|
||||
to="https://docs.hoppscotch.io/documentation/getting-started/rest/tests"
|
||||
blank
|
||||
:icon="IconExternalLink"
|
||||
reverse
|
||||
/>
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
<EnvironmentsMyDetails
|
||||
:show="showMyEnvironmentDetailsModal"
|
||||
|
||||
@@ -149,12 +149,14 @@
|
||||
:alt="`${t('empty.body')}`"
|
||||
:text="t('empty.body')"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
filled
|
||||
:label="`${t('add.new')}`"
|
||||
:icon="IconPlus"
|
||||
@click="addUrlEncodedParam"
|
||||
/>
|
||||
<template #body>
|
||||
<HoppButtonSecondary
|
||||
filled
|
||||
:label="`${t('add.new')}`"
|
||||
:icon="IconPlus"
|
||||
@click="addUrlEncodedParam"
|
||||
/>
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,55 +5,57 @@
|
||||
:heading="t('error.network_fail')"
|
||||
large
|
||||
>
|
||||
<div class="my-1 flex flex-col items-center text-secondaryLight">
|
||||
<span>
|
||||
{{ t("error.please_install_extension") }}
|
||||
</span>
|
||||
<span>
|
||||
{{ t("error.check_how_to_add_origin") }}
|
||||
<HoppSmartLink
|
||||
blank
|
||||
to="https://docs.hoppscotch.io/documentation/features/interceptor#browser-extension"
|
||||
class="text-accent hover:text-accentDark"
|
||||
>
|
||||
here
|
||||
</HoppSmartLink>
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex flex-col space-y-2 py-4">
|
||||
<span>
|
||||
<HoppSmartItem
|
||||
to="https://chrome.google.com/webstore/detail/hoppscotch-browser-extens/amknoiejhlmhancpahfcfcfhllgkpbld"
|
||||
blank
|
||||
:icon="IconChrome"
|
||||
label="Chrome"
|
||||
:info-icon="hasChromeExtInstalled ? IconCheckCircle : null"
|
||||
:active-info-icon="hasChromeExtInstalled"
|
||||
outline
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
<HoppSmartItem
|
||||
to="https://addons.mozilla.org/en-US/firefox/addon/hoppscotch"
|
||||
blank
|
||||
:icon="IconFirefox"
|
||||
label="Firefox"
|
||||
:info-icon="hasFirefoxExtInstalled ? IconCheckCircle : null"
|
||||
:active-info-icon="hasFirefoxExtInstalled"
|
||||
outline
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div class="space-y-4 py-4">
|
||||
<div class="flex items-center">
|
||||
<HoppSmartToggle
|
||||
:on="extensionEnabled"
|
||||
@change="extensionEnabled = !extensionEnabled"
|
||||
>
|
||||
{{ t("settings.extensions_use_toggle") }}
|
||||
</HoppSmartToggle>
|
||||
<template #body>
|
||||
<div class="my-1 flex flex-col items-center text-secondaryLight">
|
||||
<span>
|
||||
{{ t("error.please_install_extension") }}
|
||||
</span>
|
||||
<span>
|
||||
{{ t("error.check_how_to_add_origin") }}
|
||||
<HoppSmartLink
|
||||
blank
|
||||
to="https://docs.hoppscotch.io/documentation/features/interceptor#browser-extension"
|
||||
class="text-accent hover:text-accentDark"
|
||||
>
|
||||
here
|
||||
</HoppSmartLink>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col space-y-2 py-4">
|
||||
<span>
|
||||
<HoppSmartItem
|
||||
to="https://chrome.google.com/webstore/detail/hoppscotch-browser-extens/amknoiejhlmhancpahfcfcfhllgkpbld"
|
||||
blank
|
||||
:icon="IconChrome"
|
||||
label="Chrome"
|
||||
:info-icon="hasChromeExtInstalled ? IconCheckCircle : null"
|
||||
:active-info-icon="hasChromeExtInstalled"
|
||||
outline
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
<HoppSmartItem
|
||||
to="https://addons.mozilla.org/en-US/firefox/addon/hoppscotch"
|
||||
blank
|
||||
:icon="IconFirefox"
|
||||
label="Firefox"
|
||||
:info-icon="hasFirefoxExtInstalled ? IconCheckCircle : null"
|
||||
:active-info-icon="hasFirefoxExtInstalled"
|
||||
outline
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div class="space-y-4 py-4">
|
||||
<div class="flex items-center">
|
||||
<HoppSmartToggle
|
||||
:on="extensionEnabled"
|
||||
@change="extensionEnabled = !extensionEnabled"
|
||||
>
|
||||
{{ t("settings.extensions_use_toggle") }}
|
||||
</HoppSmartToggle>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
</div>
|
||||
<div
|
||||
v-else-if="myTeams.length"
|
||||
class="bg-info flex flex-col space-y-2 rounded-lg border border-red-500 p-4 text-secondaryDark"
|
||||
class="bg-bannerInfo flex flex-col space-y-2 rounded-lg border border-red-500 p-4 text-secondaryDark"
|
||||
>
|
||||
<h2 class="font-bold text-red-500">
|
||||
{{ t("error.danger_zone") }}
|
||||
@@ -45,7 +45,7 @@
|
||||
</div>
|
||||
<div v-else>
|
||||
<div
|
||||
class="bg-info mb-4 flex flex-col space-y-2 rounded-lg border border-red-500 p-4 text-secondaryDark"
|
||||
class="bg-bannerInfo mb-4 flex flex-col space-y-2 rounded-lg border border-red-500 p-4 text-secondaryDark"
|
||||
>
|
||||
<h2 class="font-bold text-red-500">
|
||||
{{ t("error.danger_zone") }}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="selectedWidget"
|
||||
class="divide-y divide-divider rounded border border-divider"
|
||||
class="border divide-y rounded divide-divider border-divider"
|
||||
>
|
||||
<div v-if="loading" class="px-4 py-2">
|
||||
{{ t("shared_requests.creating_widget") }}
|
||||
@@ -10,17 +10,17 @@
|
||||
{{ t("shared_requests.description") }}
|
||||
</div>
|
||||
<div class="flex flex-col divide-y divide-divider">
|
||||
<div class="flex flex-col space-y-4 p-4">
|
||||
<div class="flex flex-col p-4 space-y-4">
|
||||
<div
|
||||
v-for="widget in widgets"
|
||||
:key="widget.value"
|
||||
class="flex cursor-pointer flex-col space-y-2 rounded border border-divider px-4 py-3 hover:bg-dividerLight"
|
||||
class="flex flex-col p-4 border rounded cursor-pointer border-divider hover:bg-dividerLight"
|
||||
:class="{
|
||||
'!border-accentLight': selectedWidget.value === widget.value,
|
||||
}"
|
||||
@click="selectedWidget = widget"
|
||||
>
|
||||
<span class="text-md font-bold">
|
||||
<span class="mb-1 font-bold text-secondaryDark">
|
||||
{{ widget.label }}
|
||||
</span>
|
||||
<span class="text-tiny">
|
||||
@@ -28,9 +28,13 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col divide-y divide-divider">
|
||||
<div class="px-4 py-3">{{ t("shared_requests.preview") }}</div>
|
||||
<div class="flex flex-col items-center justify-center px-4 py-10">
|
||||
<div class="flex flex-col items-center justify-center p-4">
|
||||
<span
|
||||
class="flex justify-center flex-1 mb-2 text-secondaryLight text-tiny"
|
||||
>
|
||||
{{ t("shared_requests.preview") }}
|
||||
</span>
|
||||
<div class="w-full">
|
||||
<ShareTemplatesEmbeds
|
||||
v-if="selectedWidget.value === 'embed'"
|
||||
:endpoint="request?.endpoint"
|
||||
@@ -132,7 +136,7 @@ const embedOption = ref<EmbedOption>({
|
||||
{
|
||||
value: "authorization",
|
||||
label: t("tab.authorization"),
|
||||
enabled: true,
|
||||
enabled: false,
|
||||
},
|
||||
],
|
||||
theme: "system",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="selectedWidget"
|
||||
class="divide-y divide-divider rounded border border-divider"
|
||||
class="border divide-y rounded divide-divider border-divider"
|
||||
>
|
||||
<div v-if="loading" class="px-4 py-2">
|
||||
{{ t("shared_requests.creating_widget") }}
|
||||
@@ -14,7 +14,7 @@
|
||||
<span class="text-secondaryLight">{{ t("state.loading") }}</span>
|
||||
</div>
|
||||
<div v-else class="flex flex-col divide-y divide-divider">
|
||||
<div class="flex flex-col space-y-4 p-4">
|
||||
<div class="flex flex-col p-2 space-y-2">
|
||||
<HoppSmartRadioGroup
|
||||
v-model="selectedWidget.value"
|
||||
:radios="widgets"
|
||||
@@ -22,9 +22,9 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col divide-y divide-divider">
|
||||
<div class="flex items-center justify-center px-4 py-8">
|
||||
<div v-if="selectedWidget.value === 'embed'" class="w-full flex-1">
|
||||
<div class="flex flex-col pb-8">
|
||||
<div class="flex items-center justify-center px-6 py-4">
|
||||
<div v-if="selectedWidget.value === 'embed'" class="w-full">
|
||||
<div class="flex flex-col pb-4">
|
||||
<div
|
||||
v-for="option in embedOptions.tabs"
|
||||
:key="option.value"
|
||||
@@ -36,7 +36,8 @@
|
||||
<HoppSmartCheckbox
|
||||
:on="option.enabled"
|
||||
@change="removeEmbedOption(option.value)"
|
||||
/>
|
||||
>
|
||||
</HoppSmartCheckbox>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span>
|
||||
@@ -49,13 +50,11 @@
|
||||
theme="popover"
|
||||
:on-shown="() => tippyActions!.focus()"
|
||||
>
|
||||
<span class="select-wrapper">
|
||||
<HoppButtonSecondary
|
||||
class="ml-2 rounded-none pr-8 capitalize"
|
||||
:label="embedOptions.theme"
|
||||
:icon="embedThemeIcon"
|
||||
/>
|
||||
</span>
|
||||
<HoppButtonSecondary
|
||||
class="!py-2 !px-0 capitalize"
|
||||
:label="embedOptions.theme"
|
||||
:icon="embedThemeIcon"
|
||||
/>
|
||||
<template #content="{ hide }">
|
||||
<div
|
||||
ref="tippyActions"
|
||||
@@ -102,14 +101,20 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
class="flex justify-center mb-2 text-secondaryLight text-tiny"
|
||||
>
|
||||
{{ t("shared_requests.preview") }}
|
||||
</span>
|
||||
<ShareTemplatesEmbeds
|
||||
:endpoint="request?.endpoint"
|
||||
:method="request?.method"
|
||||
:model-value="embedOptions"
|
||||
/>
|
||||
<div class="flex items-center justify-center py-4">
|
||||
<div class="flex items-center justify-center">
|
||||
<HoppButtonSecondary
|
||||
:label="t('shared_requests.copy_html')"
|
||||
class="underline text-secondaryDark"
|
||||
@click="
|
||||
copyContent({
|
||||
widget: 'embed',
|
||||
@@ -126,12 +131,18 @@
|
||||
<div
|
||||
v-for="variant in buttonVariants"
|
||||
:key="variant.id"
|
||||
class="flex flex-col space-y-4"
|
||||
class="flex flex-col"
|
||||
>
|
||||
<span
|
||||
class="flex justify-center mb-2 text-secondaryLight text-tiny"
|
||||
>
|
||||
{{ t("shared_requests.preview") }}
|
||||
</span>
|
||||
<ShareTemplatesButton :img="variant.img" />
|
||||
<div class="flex items-center justify-between">
|
||||
<HoppButtonSecondary
|
||||
:label="t('shared_requests.copy_html')"
|
||||
class="underline text-secondaryDark"
|
||||
@click="
|
||||
copyContent({
|
||||
widget: 'button',
|
||||
@@ -142,6 +153,7 @@
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
:label="t('shared_requests.copy_markdown')"
|
||||
class="underline text-secondaryDark"
|
||||
@click="
|
||||
copyContent({
|
||||
widget: 'button',
|
||||
@@ -157,12 +169,17 @@
|
||||
<div
|
||||
v-for="variant in linkVariants"
|
||||
:key="variant.type"
|
||||
class="flex flex-col items-center justify-center space-y-2"
|
||||
class="flex flex-col items-center justify-center"
|
||||
>
|
||||
<span
|
||||
class="flex justify-center mb-2 text-secondaryLight text-tiny"
|
||||
>
|
||||
{{ t("shared_requests.preview") }}
|
||||
</span>
|
||||
<ShareTemplatesLink :link="variant.link" :label="variant.label" />
|
||||
|
||||
<HoppButtonSecondary
|
||||
:label="t(`shared_requests.copy_${variant.type}`)"
|
||||
class="underline text-secondaryDark"
|
||||
@click="
|
||||
copyContent({
|
||||
widget: 'link',
|
||||
@@ -205,6 +222,35 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
embedOptions: {
|
||||
type: Object as PropType<EmbedOption>,
|
||||
default: () => ({
|
||||
selectedTab: "parameters",
|
||||
tabs: [
|
||||
{
|
||||
value: "parameters",
|
||||
label: "shared_requests.parameters",
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
value: "body",
|
||||
label: "shared_requests.body",
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
value: "headers",
|
||||
label: "shared_requests.headers",
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
value: "authorization",
|
||||
label: "shared_requests.authorization",
|
||||
enabled: false,
|
||||
},
|
||||
],
|
||||
theme: "system",
|
||||
}),
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -220,6 +266,7 @@ const emit = defineEmits<{
|
||||
}>()
|
||||
|
||||
const selectedWidget = useVModel(props, "modelValue")
|
||||
const embedOptions = useVModel(props, "embedOptions")
|
||||
|
||||
type WidgetID = "embed" | "button" | "link"
|
||||
|
||||
@@ -254,42 +301,13 @@ type EmbedOption = {
|
||||
}[]
|
||||
theme: "light" | "dark" | "system"
|
||||
}
|
||||
|
||||
const embedOptions = ref<EmbedOption>({
|
||||
selectedTab: "parameters",
|
||||
tabs: [
|
||||
{
|
||||
value: "parameters",
|
||||
label: t("tab.parameters"),
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
value: "body",
|
||||
label: t("tab.body"),
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
value: "headers",
|
||||
label: t("tab.headers"),
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
value: "authorization",
|
||||
label: t("tab.authorization"),
|
||||
enabled: true,
|
||||
},
|
||||
],
|
||||
theme: "system",
|
||||
})
|
||||
|
||||
const embedThemeIcon = computed(() => {
|
||||
if (embedOptions.value.theme === "system") {
|
||||
return IconMonitor
|
||||
} else if (embedOptions.value.theme === "light") {
|
||||
return IconSun
|
||||
} else {
|
||||
return IconMoon
|
||||
}
|
||||
return IconMoon
|
||||
})
|
||||
|
||||
const removeEmbedOption = (option: EmbedTabs) => {
|
||||
@@ -352,15 +370,11 @@ const linkVariants: LinkVariant[] = [
|
||||
},
|
||||
]
|
||||
|
||||
const baseURL = import.meta.env.VITE_SHORTCODE_BASE_URL ?? "https://hopp.sh"
|
||||
const shortcodeBaseURL =
|
||||
import.meta.env.VITE_SHORTCODE_BASE_URL ?? "https://hopp.sh"
|
||||
|
||||
const copyEmbed = () => {
|
||||
const options = embedOptions.value
|
||||
const enabledEmbedOptions = options.tabs
|
||||
.filter((tab) => tab.enabled)
|
||||
.map((tab) => tab.value)
|
||||
.toString()
|
||||
return `<iframe src="${baseURL}/e/${props.request?.id}/${enabledEmbedOptions}' style='width: 100%; height: 500px; border: 0; border-radius: 4px; overflow: hidden;'></iframe>`
|
||||
return `<iframe src="${shortcodeBaseURL}/e/${props.request?.id}" title="Hoppscotch Embed" style="width: 100%; height: 480px; border-radius: 4px; border: 1px solid rgba(0, 0, 0, 0.1);"></iframe>`
|
||||
}
|
||||
|
||||
const copyButton = (
|
||||
@@ -377,20 +391,18 @@ const copyButton = (
|
||||
}
|
||||
|
||||
if (type === "markdown") {
|
||||
return `[](${baseURL}/r/${props.request?.id})`
|
||||
} else {
|
||||
return `<a href="${baseURL}/r/${props.request?.id}"><img src="${baseURL}/${badge}" alt="Run in Hoppscotch" /></a>`
|
||||
return `[](${shortcodeBaseURL}/r/${props.request?.id})`
|
||||
}
|
||||
return `<a href="${shortcodeBaseURL}/r/${props.request?.id}"><img src="${shortcodeBaseURL}/${badge}" alt="Run in Hoppscotch" /></a>`
|
||||
}
|
||||
|
||||
const copyLink = (variationID: string) => {
|
||||
if (variationID === "link1") {
|
||||
return `${baseURL}/r/${props.request?.id}`
|
||||
return `${shortcodeBaseURL}/r/${props.request?.id}`
|
||||
} else if (variationID === "link2") {
|
||||
return `<a href="${baseURL}/r/${props.request?.id}">Run in Hoppscotch</a>`
|
||||
} else {
|
||||
return `[Run in Hoppscotch](${baseURL}/r/${props.request?.id})`
|
||||
return `<a href="${shortcodeBaseURL}/r/${props.request?.id}">Run in Hoppscotch</a>`
|
||||
}
|
||||
return `[Run in Hoppscotch](${shortcodeBaseURL}/r/${props.request?.id})`
|
||||
}
|
||||
|
||||
const copyContent = ({
|
||||
@@ -410,7 +422,10 @@ const copyContent = ({
|
||||
} else {
|
||||
content = copyEmbed()
|
||||
}
|
||||
const copyContent = { sharedRequestID: props.request?.id, content }
|
||||
const copyContent = {
|
||||
sharedRequestID: props.request?.id,
|
||||
content,
|
||||
}
|
||||
emit("copy-shared-request", copyContent)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
<HoppSmartModal
|
||||
v-if="show"
|
||||
dialog
|
||||
:title="t('modal.share_request')"
|
||||
:title="
|
||||
step === 1 ? t('modal.share_request') : t('modal.customize_request')
|
||||
"
|
||||
styles="sm:max-w-md"
|
||||
@close="hideModal"
|
||||
>
|
||||
@@ -21,14 +23,14 @@
|
||||
<ShareCustomizeModal
|
||||
v-else-if="step === 2"
|
||||
v-model="selectedWidget"
|
||||
v-model:embed-options="embedOptions"
|
||||
:request="request"
|
||||
:loading="loading"
|
||||
@copy-shared-request="copySharedRequest"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<div v-if="step === 1" class="flex justify-end">
|
||||
<template v-if="step === 1" #footer>
|
||||
<div class="flex justify-start flex-1">
|
||||
<HoppButtonPrimary
|
||||
:label="t('action.create')"
|
||||
:loading="loading"
|
||||
@@ -36,11 +38,12 @@
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
:label="t('action.cancel')"
|
||||
class="mr-2"
|
||||
class="ml-2"
|
||||
filled
|
||||
outline
|
||||
@click="hideModal"
|
||||
/>
|
||||
</div>
|
||||
<HoppButtonPrimary v-else :label="t('action.close')" @click="hideModal" />
|
||||
</template>
|
||||
</HoppSmartModal>
|
||||
</template>
|
||||
@@ -53,6 +56,18 @@ import { useI18n } from "~/composables/i18n"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
type EmbedTabs = "parameters" | "body" | "headers" | "authorization"
|
||||
|
||||
type EmbedOption = {
|
||||
selectedTab: EmbedTabs
|
||||
tabs: {
|
||||
value: EmbedTabs
|
||||
label: string
|
||||
enabled: boolean
|
||||
}[]
|
||||
theme: "light" | "dark" | "system"
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
request: {
|
||||
type: Object as PropType<HoppRESTRequest | null>,
|
||||
@@ -75,6 +90,35 @@ const props = defineProps({
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
embedOptions: {
|
||||
type: Object as PropType<EmbedOption>,
|
||||
default: () => ({
|
||||
selectedTab: "parameters",
|
||||
tabs: [
|
||||
{
|
||||
value: "parameters",
|
||||
label: "shared_requests.parameters",
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
value: "body",
|
||||
label: "shared_requests.body",
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
value: "headers",
|
||||
label: "shared_requests.headers",
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
value: "authorization",
|
||||
label: "shared_requests.authorization",
|
||||
enabled: false,
|
||||
},
|
||||
],
|
||||
theme: "system",
|
||||
}),
|
||||
},
|
||||
})
|
||||
|
||||
type WidgetID = "embed" | "button" | "link"
|
||||
@@ -86,6 +130,7 @@ type Widget = {
|
||||
}
|
||||
|
||||
const selectedWidget = useVModel(props, "modelValue")
|
||||
const embedOptions = useVModel(props, "embedOptions")
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "create-shared-request", request: HoppRESTRequest | null): void
|
||||
@@ -94,7 +139,7 @@ const emit = defineEmits<{
|
||||
(e: "update:step", value: number): void
|
||||
(
|
||||
e: "copy-shared-request",
|
||||
request: {
|
||||
payload: {
|
||||
sharedRequestID: string | undefined
|
||||
content: string | undefined
|
||||
}
|
||||
@@ -105,11 +150,11 @@ const createSharedRequest = () => {
|
||||
emit("create-shared-request", props.request as HoppRESTRequest)
|
||||
}
|
||||
|
||||
const copySharedRequest = (request: {
|
||||
const copySharedRequest = (payload: {
|
||||
sharedRequestID: string | undefined
|
||||
content: string | undefined
|
||||
}) => {
|
||||
emit("copy-shared-request", request)
|
||||
emit("copy-shared-request", payload)
|
||||
}
|
||||
|
||||
const hideModal = () => {
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
<template>
|
||||
<div
|
||||
class="group flex items-stretch"
|
||||
class="flex items-stretch group"
|
||||
@contextmenu.prevent="options!.tippy.show()"
|
||||
>
|
||||
<div
|
||||
v-tippy="{ theme: 'tooltip', delay: [500, 20] }"
|
||||
class="pointer-events-auto flex min-w-0 flex-1 cursor-pointer items-center justify-center py-2"
|
||||
class="flex items-center justify-center flex-1 min-w-0 py-2 cursor-pointer pointer-events-auto"
|
||||
:title="`${timeStamp}`"
|
||||
@click="openInNewTab"
|
||||
>
|
||||
<span
|
||||
class="pointer-events-none flex w-16 items-center justify-center truncate px-2"
|
||||
class="flex items-center justify-center w-16 px-2 truncate pointer-events-none"
|
||||
:style="{ color: requestLabelColor }"
|
||||
>
|
||||
<span class="truncate text-tiny font-semibold">
|
||||
<span class="font-semibold truncate text-tiny">
|
||||
{{ parseRequest.method }}
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="pointer-events-none flex min-w-0 flex-1 items-center pr-2 transition group-hover:text-secondaryDark"
|
||||
class="flex items-center flex-1 min-w-0 pr-2 transition pointer-events-none group-hover:text-secondaryDark"
|
||||
>
|
||||
<span class="flex-1 truncate">
|
||||
{{ parseRequest.endpoint }}
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="flex-1 truncate border-l border-dividerDark px-2 text-secondaryLight group-hover:text-secondaryDark"
|
||||
class="flex px-2 truncate text-secondaryLight group-hover:text-secondaryDark"
|
||||
>
|
||||
{{ parseRequest.name }}
|
||||
</span>
|
||||
@@ -69,7 +69,7 @@
|
||||
/>
|
||||
<HoppSmartItem
|
||||
ref="customizeAction"
|
||||
:icon="IconFileEdit"
|
||||
:icon="IconCustomize"
|
||||
:label="`${t('shared_requests.customize')}`"
|
||||
:shortcut="['E']"
|
||||
@click="
|
||||
@@ -110,7 +110,7 @@ import { getMethodLabelColorClassOf } from "~/helpers/rest/labelColoring"
|
||||
import { Shortcode } from "~/helpers/shortcode/Shortcode"
|
||||
import IconArrowUpRight from "~icons/lucide/arrow-up-right-square"
|
||||
import IconMoreVertical from "~icons/lucide/more-vertical"
|
||||
import IconFileEdit from "~icons/lucide/file-edit"
|
||||
import IconCustomize from "~icons/lucide/settings-2"
|
||||
import IconTrash2 from "~icons/lucide/trash-2"
|
||||
import { shortDateTime } from "~/helpers/utils/date"
|
||||
|
||||
@@ -121,7 +121,12 @@ const props = defineProps<{
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "customize-shared-request", request: HoppRESTRequest, id: string): void
|
||||
(
|
||||
e: "customize-shared-request",
|
||||
request: HoppRESTRequest,
|
||||
id: string,
|
||||
embedProperties?: string | null
|
||||
): void
|
||||
(e: "delete-shared-request", codeID: string): void
|
||||
(e: "open-new-tab", request: HoppRESTRequest): void
|
||||
}>()
|
||||
@@ -130,6 +135,7 @@ const tippyActions = ref<TippyComponent | null>(null)
|
||||
const openInNewTabAction = ref<HTMLButtonElement | null>(null)
|
||||
const customizeAction = ref<HTMLButtonElement | null>(null)
|
||||
const deleteAction = ref<HTMLButtonElement | null>(null)
|
||||
const options = ref<any | null>(null)
|
||||
|
||||
const parseRequest = computed(() =>
|
||||
pipe(props.request.request, JSON.parse, translateToNewRequest)
|
||||
@@ -144,7 +150,13 @@ const openInNewTab = () => {
|
||||
}
|
||||
|
||||
const customizeSharedRequest = () => {
|
||||
emit("customize-shared-request", parseRequest.value, props.request.id)
|
||||
const embedProperties = props.request.properties
|
||||
emit(
|
||||
"customize-shared-request",
|
||||
parseRequest.value,
|
||||
props.request.id,
|
||||
embedProperties
|
||||
)
|
||||
}
|
||||
|
||||
const deleteSharedRequest = () => {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
to="https://docs.hoppscotch.io/documentation/features/shared-request"
|
||||
to="https://docs.hoppscotch.io/documentation/features/share-requests"
|
||||
blank
|
||||
:title="t('app.wiki')"
|
||||
:icon="IconHelpCircle"
|
||||
@@ -32,10 +32,12 @@
|
||||
:alt="`${t('empty.shared_requests_logout')}`"
|
||||
:text="`${t('empty.shared_requests_logout')}`"
|
||||
>
|
||||
<HoppButtonPrimary
|
||||
:label="t('auth.login')"
|
||||
@click="invokeAction('modals.login.toggle')"
|
||||
/>
|
||||
<template #body>
|
||||
<HoppButtonPrimary
|
||||
:label="t('auth.login')"
|
||||
@click="invokeAction('modals.login.toggle')"
|
||||
/>
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
|
||||
<template v-else-if="sharedRequests.length">
|
||||
@@ -80,11 +82,12 @@
|
||||
/>
|
||||
<ShareModal
|
||||
v-model="selectedWidget"
|
||||
v-model:embed-options="embedOptions"
|
||||
:step="step"
|
||||
:request="requestToShare"
|
||||
:show="showShareRequestModal"
|
||||
:loading="shareRequestCreatingLoading"
|
||||
:step="step"
|
||||
@hide-modal="displayCustomizeRequestModal(false)"
|
||||
@hide-modal="displayCustomizeRequestModal(false, null)"
|
||||
@copy-shared-request="copySharedRequest"
|
||||
@create-shared-request="createSharedRequest"
|
||||
/>
|
||||
@@ -105,6 +108,7 @@ import * as TE from "fp-ts/TaskEither"
|
||||
import {
|
||||
deleteShortcode as backendDeleteShortcode,
|
||||
createShortcode,
|
||||
updateEmbedProperties,
|
||||
} from "~/helpers/backend/mutations/Shortcode"
|
||||
import { GQLError } from "~/helpers/backend/GQLClient"
|
||||
import { useToast } from "~/composables/toast"
|
||||
@@ -114,6 +118,7 @@ import { copyToClipboard } from "~/helpers/utils/clipboard"
|
||||
import * as E from "fp-ts/Either"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
import { useService } from "dioc/vue"
|
||||
import { watch } from "vue"
|
||||
|
||||
const t = useI18n()
|
||||
const colorMode = useColorMode()
|
||||
@@ -130,6 +135,70 @@ const shareRequestCreatingLoading = ref(false)
|
||||
|
||||
const requestToShare = ref<HoppRESTRequest | null>(null)
|
||||
|
||||
const embedOptions = ref<EmbedOption>({
|
||||
selectedTab: "parameters",
|
||||
tabs: [
|
||||
{
|
||||
value: "parameters",
|
||||
label: t("tab.parameters"),
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
value: "body",
|
||||
label: t("tab.body"),
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
value: "headers",
|
||||
label: t("tab.headers"),
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
value: "authorization",
|
||||
label: t("tab.authorization"),
|
||||
enabled: false,
|
||||
},
|
||||
],
|
||||
theme: "system",
|
||||
})
|
||||
|
||||
const updateEmbedProperty = async (
|
||||
shareRequestID: string,
|
||||
properties: string
|
||||
) => {
|
||||
const customizeEmbedResult = await updateEmbedProperties(
|
||||
shareRequestID,
|
||||
properties
|
||||
)()
|
||||
|
||||
if (E.isLeft(customizeEmbedResult)) {
|
||||
toast.error(`${customizeEmbedResult.left.error}`)
|
||||
toast.error(t("error.something_went_wrong"))
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => embedOptions.value,
|
||||
() => {
|
||||
if (
|
||||
requestToShare.value &&
|
||||
requestToShare.value.id &&
|
||||
showShareRequestModal.value
|
||||
) {
|
||||
if (selectedWidget.value.value === "embed") {
|
||||
const properties = {
|
||||
options: embedOptions.value.tabs
|
||||
.filter((tab) => tab.enabled)
|
||||
.map((tab) => tab.value),
|
||||
theme: embedOptions.value.theme,
|
||||
}
|
||||
updateEmbedProperty(requestToShare.value.id, JSON.stringify(properties))
|
||||
}
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
const restTab = useService(RESTTabService)
|
||||
|
||||
const currentUser = useReadonlyStream(
|
||||
@@ -139,6 +208,18 @@ const currentUser = useReadonlyStream(
|
||||
|
||||
const step = ref(1)
|
||||
|
||||
type EmbedTabs = "parameters" | "body" | "headers" | "authorization"
|
||||
|
||||
type EmbedOption = {
|
||||
selectedTab: EmbedTabs
|
||||
tabs: {
|
||||
value: EmbedTabs
|
||||
label: string
|
||||
enabled: boolean
|
||||
}[]
|
||||
theme: "light" | "dark" | "system"
|
||||
}
|
||||
|
||||
type WidgetID = "embed" | "button" | "link"
|
||||
|
||||
type Widget = {
|
||||
@@ -218,15 +299,73 @@ const displayShareRequestModal = (show: boolean) => {
|
||||
showShareRequestModal.value = show
|
||||
step.value = 1
|
||||
}
|
||||
const displayCustomizeRequestModal = (show: boolean) => {
|
||||
|
||||
const displayCustomizeRequestModal = (
|
||||
show: boolean,
|
||||
embedProperties?: string | null
|
||||
) => {
|
||||
showShareRequestModal.value = show
|
||||
step.value = 2
|
||||
if (!embedProperties) {
|
||||
selectedWidget.value = {
|
||||
value: "button",
|
||||
label: t("shared_requests.button"),
|
||||
info: t("shared_requests.button_info"),
|
||||
}
|
||||
embedOptions.value = {
|
||||
selectedTab: "parameters",
|
||||
tabs: [
|
||||
{
|
||||
value: "parameters",
|
||||
label: t("tab.parameters"),
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
value: "body",
|
||||
label: t("tab.body"),
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
value: "headers",
|
||||
label: t("tab.headers"),
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
value: "authorization",
|
||||
label: t("tab.authorization"),
|
||||
enabled: false,
|
||||
},
|
||||
],
|
||||
theme: "system",
|
||||
}
|
||||
} else {
|
||||
const parsedEmbedProperties = JSON.parse(embedProperties)
|
||||
embedOptions.value = {
|
||||
selectedTab: parsedEmbedProperties.options[0],
|
||||
tabs: embedOptions.value.tabs.map((tab) => {
|
||||
return {
|
||||
...tab,
|
||||
enabled: parsedEmbedProperties.options.includes(tab.value),
|
||||
}
|
||||
}),
|
||||
theme: parsedEmbedProperties.theme,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const createSharedRequest = async (request: HoppRESTRequest | null) => {
|
||||
if (request && selectedWidget.value) {
|
||||
const properties = {
|
||||
options: ["parameters", "body", "headers"],
|
||||
theme: "system",
|
||||
}
|
||||
shareRequestCreatingLoading.value = true
|
||||
const sharedRequestResult = await createShortcode(request)()
|
||||
const sharedRequestResult = await createShortcode(
|
||||
request,
|
||||
selectedWidget.value.value === "embed"
|
||||
? JSON.stringify(properties)
|
||||
: undefined
|
||||
)()
|
||||
|
||||
platform.analytics?.logEvent({
|
||||
type: "HOPP_SHORTCODE_CREATED",
|
||||
@@ -243,6 +382,23 @@ const createSharedRequest = async (request: HoppRESTRequest | null) => {
|
||||
id: sharedRequestResult.right.createShortcode.id,
|
||||
}
|
||||
step.value = 2
|
||||
|
||||
if (sharedRequestResult.right.createShortcode.properties) {
|
||||
const parsedEmbedProperties = JSON.parse(
|
||||
sharedRequestResult.right.createShortcode.properties
|
||||
)
|
||||
|
||||
embedOptions.value = {
|
||||
selectedTab: parsedEmbedProperties.options[0],
|
||||
tabs: embedOptions.value.tabs.map((tab) => {
|
||||
return {
|
||||
...tab,
|
||||
enabled: parsedEmbedProperties.options.includes(tab.value),
|
||||
}
|
||||
}),
|
||||
theme: parsedEmbedProperties.theme,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -250,21 +406,22 @@ const createSharedRequest = async (request: HoppRESTRequest | null) => {
|
||||
|
||||
const customizeSharedRequest = (
|
||||
request: HoppRESTRequest,
|
||||
shredRequestID: string
|
||||
shredRequestID: string,
|
||||
embedProperties?: string | null
|
||||
) => {
|
||||
requestToShare.value = {
|
||||
...request,
|
||||
id: shredRequestID,
|
||||
}
|
||||
displayCustomizeRequestModal(true)
|
||||
displayCustomizeRequestModal(true, embedProperties)
|
||||
}
|
||||
|
||||
const copySharedRequest = (request: {
|
||||
const copySharedRequest = (payload: {
|
||||
sharedRequestID: string | undefined
|
||||
content: string | undefined
|
||||
}) => {
|
||||
if (request.content) {
|
||||
copyToClipboard(request.content)
|
||||
if (payload.content) {
|
||||
copyToClipboard(payload.content)
|
||||
toast.success(t("state.copied_to_clipboard"))
|
||||
}
|
||||
}
|
||||
@@ -291,13 +448,12 @@ const resolveConfirmModal = (title: string | null) => {
|
||||
const getErrorMessage = (err: GQLError<string>) => {
|
||||
if (err.type === "network_error") {
|
||||
return t("error.network_error")
|
||||
} else {
|
||||
switch (err.error) {
|
||||
case "shortcode/not_found":
|
||||
return t("shared_request.not_found")
|
||||
default:
|
||||
return t("error.something_went_wrong")
|
||||
}
|
||||
}
|
||||
switch (err.error) {
|
||||
case "shortcode/not_found":
|
||||
return t("shared_request.not_found")
|
||||
default:
|
||||
return t("error.something_went_wrong")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex items-center justify-center rounded border border-dotted border-dividerDark p-5"
|
||||
>
|
||||
<a href="/" target="_blank">
|
||||
<img :src="img" :alt="t('shared_requests.run_in_hoppscotch')" />
|
||||
</a>
|
||||
<div class="flex flex-col items-center p-4 border rounded border-dividerDark">
|
||||
<img :src="img" :alt="t('shared_requests.run_in_hoppscotch')" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col rounded border border-dotted border-divider p-5"
|
||||
class="flex flex-col p-4 border rounded border-dividerDark"
|
||||
:class="{
|
||||
'bg-accentContrast': isEmbedThemeLight,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="flex items-stretch space-x-4 rounded border-divider"
|
||||
class="flex items-stretch space-x-2"
|
||||
:class="{
|
||||
'bg-accentContrast': isEmbedThemeLight,
|
||||
}"
|
||||
>
|
||||
<span
|
||||
class="flex max-w-[4rem] items-center justify-center rounded border border-divider px-1 py-2 text-tiny"
|
||||
:class="{
|
||||
'!border-dividerLight bg-accentContrast text-primary':
|
||||
isEmbedThemeLight,
|
||||
}"
|
||||
>
|
||||
<span class="truncate">
|
||||
{{ method }}
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="flex max-w-46 items-center rounded border border-divider p-2"
|
||||
class="flex items-center flex-1 min-w-0 border rounded border-divider"
|
||||
>
|
||||
<span
|
||||
class="min-w-0 truncate"
|
||||
class="flex max-w-[4rem] rounded-l h-full items-center justify-center border-r border-divider text-tiny"
|
||||
:class="{
|
||||
'!border-dividerLight bg-accentContrast text-primary':
|
||||
isEmbedThemeLight,
|
||||
}"
|
||||
>
|
||||
<span class="px-3 truncate">
|
||||
{{ method }}
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="px-3 truncate"
|
||||
:class="{
|
||||
'text-primary': isEmbedThemeLight,
|
||||
}"
|
||||
@@ -35,7 +35,7 @@
|
||||
</span>
|
||||
</span>
|
||||
<button
|
||||
class="flex items-center justify-center rounded border border-dividerDark bg-primaryDark px-3 py-2 font-semibold text-secondary"
|
||||
class="flex items-center justify-center flex-shrink-0 px-3 py-2 font-semibold border rounded border-dividerDark bg-primaryDark text-secondary"
|
||||
:class="{
|
||||
'!bg-accentContrast text-primaryLight': isEmbedThemeLight,
|
||||
}"
|
||||
@@ -44,10 +44,10 @@
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="flex border-divider"
|
||||
class="flex"
|
||||
:class="{
|
||||
'bg-accentContrast text-primary': isEmbedThemeLight,
|
||||
'border-b pt-2 ': !noActiveTab,
|
||||
'border-b border-divider pt-2': !noActiveTab,
|
||||
}"
|
||||
>
|
||||
<span
|
||||
@@ -57,7 +57,8 @@
|
||||
class="px-2 py-2"
|
||||
:class="{
|
||||
'border-b border-dividerDark':
|
||||
embedOptions.selectedTab === option.value,
|
||||
embedOptions.tabs.filter((tab) => tab.enabled)[0]?.value ===
|
||||
option.value,
|
||||
}"
|
||||
>
|
||||
{{ option.label }}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex items-center justify-center rounded border border-dotted border-dividerDark p-5"
|
||||
>
|
||||
<div class="flex flex-col items-center p-4 border rounded border-dividerDark">
|
||||
<span
|
||||
:class="{
|
||||
'border-b border-secondary': label,
|
||||
@@ -23,7 +21,12 @@ const props = defineProps<{
|
||||
label?: string | undefined
|
||||
}>()
|
||||
|
||||
const shortcodeBaseURL =
|
||||
import.meta.env.VITE_SHORTCODE_BASE_URL ?? "https://hopp.sh"
|
||||
|
||||
const text = computed(() => {
|
||||
return props.label ? t(props.label) : `hopp.sh/r/${props.link ?? "xxxx"}`
|
||||
return props.label
|
||||
? t(props.label)
|
||||
: `${shortcodeBaseURL}/r/${props.link ?? "xxxx"}`
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
:text="`${t('state.nothing_found')} ‟${searchQuery}”`"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-lucide-search class="svg-icons pb-2 opacity-75" />
|
||||
<icon-lucide-search class="svg-icons opacity-75" />
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
</div>
|
||||
|
||||
@@ -45,15 +45,17 @@
|
||||
:alt="`${t('empty.members')}`"
|
||||
:text="t('empty.members')"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
:icon="IconUserPlus"
|
||||
:label="t('team.invite')"
|
||||
@click="
|
||||
() => {
|
||||
emit('invite-team')
|
||||
}
|
||||
"
|
||||
/>
|
||||
<template #body>
|
||||
<HoppButtonSecondary
|
||||
:icon="IconUserPlus"
|
||||
:label="t('team.invite')"
|
||||
@click="
|
||||
() => {
|
||||
emit('invite-team')
|
||||
}
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
<div v-else class="divide-y divide-dividerLight">
|
||||
<div
|
||||
|
||||
@@ -125,8 +125,7 @@
|
||||
pendingInvites.data.right.team?.teamInvitations.length === 0
|
||||
"
|
||||
:text="t('empty.pending_invites')"
|
||||
>
|
||||
</HoppSmartPlaceholder>
|
||||
/>
|
||||
<div
|
||||
v-if="!pendingInvites.loading && E.isLeft(pendingInvites.data)"
|
||||
class="flex flex-col items-center p-4"
|
||||
@@ -245,11 +244,13 @@
|
||||
:alt="`${t('empty.invites')}`"
|
||||
:text="`${t('empty.invites')}`"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
:label="t('add.new')"
|
||||
filled
|
||||
@click="addNewInvitee"
|
||||
/>
|
||||
<template #body>
|
||||
<HoppButtonSecondary
|
||||
:label="t('add.new')"
|
||||
filled
|
||||
@click="addNewInvitee"
|
||||
/>
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
</div>
|
||||
<div
|
||||
|
||||
@@ -16,11 +16,13 @@
|
||||
:alt="`${t('empty.teams')}`"
|
||||
:text="`${t('empty.teams')}`"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
:label="`${t('team.create_new')}`"
|
||||
filled
|
||||
@click="displayModalAdd(true)"
|
||||
/>
|
||||
<template #body>
|
||||
<HoppButtonSecondary
|
||||
:label="`${t('team.create_new')}`"
|
||||
filled
|
||||
@click="displayModalAdd(true)"
|
||||
/>
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
<div
|
||||
v-else-if="!loading"
|
||||
|
||||
@@ -21,13 +21,15 @@
|
||||
:alt="`${t('empty.teams')}`"
|
||||
:text="`${t('empty.teams')}`"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
:label="t('team.create_new')"
|
||||
filled
|
||||
outline
|
||||
:icon="IconPlus"
|
||||
@click="displayModalAdd(true)"
|
||||
/>
|
||||
<template #body>
|
||||
<HoppButtonSecondary
|
||||
:label="t('team.create_new')"
|
||||
filled
|
||||
outline
|
||||
:icon="IconPlus"
|
||||
@click="displayModalAdd(true)"
|
||||
/>
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
<div v-else-if="!loading" class="flex flex-col">
|
||||
<div
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { flow } from "fp-ts/function"
|
||||
import { useI18n as _useI18n } from "vue-i18n"
|
||||
|
||||
export const useI18n = flow(_useI18n, (x) => x.t)
|
||||
export function useI18n() {
|
||||
return _useI18n().t
|
||||
}
|
||||
|
||||
export const useFullI18n = _useI18n
|
||||
|
||||
@@ -1,26 +1,15 @@
|
||||
import { Environment } from "@hoppscotch/data"
|
||||
import { SandboxTestResult, TestDescriptor } from "@hoppscotch/js-sandbox"
|
||||
import { runTestScript } from "@hoppscotch/js-sandbox/web"
|
||||
import * as A from "fp-ts/Array"
|
||||
import * as E from "fp-ts/Either"
|
||||
import * as O from "fp-ts/Option"
|
||||
import { flow, pipe } from "fp-ts/function"
|
||||
import { cloneDeep } from "lodash-es"
|
||||
import { Observable, Subject } from "rxjs"
|
||||
import { filter } from "rxjs/operators"
|
||||
import { flow, pipe } from "fp-ts/function"
|
||||
import * as O from "fp-ts/Option"
|
||||
import * as A from "fp-ts/Array"
|
||||
import { Environment } from "@hoppscotch/data"
|
||||
import {
|
||||
SandboxTestResult,
|
||||
runTestScript,
|
||||
TestDescriptor,
|
||||
} from "@hoppscotch/js-sandbox"
|
||||
import * as E from "fp-ts/Either"
|
||||
import { cloneDeep } from "lodash-es"
|
||||
import {
|
||||
getCombinedEnvVariables,
|
||||
getFinalEnvsFromPreRequest,
|
||||
} from "./preRequest"
|
||||
import { getEffectiveRESTRequest } from "./utils/EffectiveURL"
|
||||
import { HoppRESTResponse } from "./types/HoppRESTResponse"
|
||||
import { createRESTNetworkRequestStream } from "./network"
|
||||
import { HoppTestData, HoppTestResult } from "./types/HoppTestResult"
|
||||
import { isJSONContentType } from "./utils/contenttypes"
|
||||
import { updateTeamEnvironment } from "./backend/mutations/TeamEnvironment"
|
||||
import { Ref } from "vue"
|
||||
|
||||
import {
|
||||
environmentsStore,
|
||||
getCurrentEnvironment,
|
||||
@@ -29,9 +18,18 @@ import {
|
||||
setGlobalEnvVariables,
|
||||
updateEnvironment,
|
||||
} from "~/newstore/environments"
|
||||
import { Ref } from "vue"
|
||||
import { HoppTab } from "~/services/tab"
|
||||
import { updateTeamEnvironment } from "./backend/mutations/TeamEnvironment"
|
||||
import { createRESTNetworkRequestStream } from "./network"
|
||||
import {
|
||||
getCombinedEnvVariables,
|
||||
getFinalEnvsFromPreRequest,
|
||||
} from "./preRequest"
|
||||
import { HoppRESTDocument } from "./rest/document"
|
||||
import { HoppRESTResponse } from "./types/HoppRESTResponse"
|
||||
import { HoppTestData, HoppTestResult } from "./types/HoppTestResult"
|
||||
import { getEffectiveRESTRequest } from "./utils/EffectiveURL"
|
||||
import { isJSONContentType } from "./utils/contenttypes"
|
||||
|
||||
const getTestableBody = (
|
||||
res: HoppRESTResponse & { type: "success" | "fail" }
|
||||
@@ -89,7 +87,7 @@ export function runRESTRequest$(
|
||||
const res = getFinalEnvsFromPreRequest(
|
||||
tab.value.document.request.preRequestScript,
|
||||
getCombinedEnvVariables()
|
||||
)().then((envs) => {
|
||||
).then((envs) => {
|
||||
if (cancelCalled) return E.left("cancellation" as const)
|
||||
|
||||
if (E.isLeft(envs)) {
|
||||
@@ -125,7 +123,7 @@ export function runRESTRequest$(
|
||||
body: getTestableBody(res),
|
||||
headers: res.headers,
|
||||
}
|
||||
)()
|
||||
)
|
||||
|
||||
if (E.isRight(runResult)) {
|
||||
tab.value.document.testResults = translateToSandboxTestResults(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
mutation CreateShortcode($request: String!) {
|
||||
createShortcode(request: $request) {
|
||||
mutation CreateShortcode($request: String!, $properties: String) {
|
||||
createShortcode(request: $request, properties: $properties) {
|
||||
id
|
||||
request
|
||||
createdOn
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
mutation UpdateEmbedProperties($code: ID!, $properties: String!) {
|
||||
updateEmbedProperties(code: $code, properties: $properties) {
|
||||
id
|
||||
request
|
||||
properties
|
||||
createdOn
|
||||
}
|
||||
}
|
||||
@@ -2,5 +2,6 @@ query ResolveShortcode($code: ID!) {
|
||||
shortcode(code: $code) {
|
||||
id
|
||||
request
|
||||
properties
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
subscription ShortcodeUpdated {
|
||||
myShortcodesUpdated {
|
||||
id
|
||||
request
|
||||
createdOn
|
||||
properties
|
||||
}
|
||||
}
|
||||
@@ -7,15 +7,22 @@ import {
|
||||
DeleteShortcodeDocument,
|
||||
DeleteShortcodeMutation,
|
||||
DeleteShortcodeMutationVariables,
|
||||
UpdateEmbedPropertiesDocument,
|
||||
UpdateEmbedPropertiesMutation,
|
||||
UpdateEmbedPropertiesMutationVariables,
|
||||
} from "../graphql"
|
||||
|
||||
type DeleteShortcodeErrors = "shortcode/not_found"
|
||||
|
||||
export const createShortcode = (request: HoppRESTRequest) =>
|
||||
export const createShortcode = (
|
||||
request: HoppRESTRequest,
|
||||
properties?: string
|
||||
) =>
|
||||
runMutation<CreateShortcodeMutation, CreateShortcodeMutationVariables, "">(
|
||||
CreateShortcodeDocument,
|
||||
{
|
||||
request: JSON.stringify(request),
|
||||
properties,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -27,3 +34,13 @@ export const deleteShortcode = (code: string) =>
|
||||
>(DeleteShortcodeDocument, {
|
||||
code,
|
||||
})
|
||||
|
||||
export const updateEmbedProperties = (code: string, properties: string) =>
|
||||
runMutation<
|
||||
UpdateEmbedPropertiesMutation,
|
||||
UpdateEmbedPropertiesMutationVariables,
|
||||
""
|
||||
>(UpdateEmbedPropertiesDocument, {
|
||||
code,
|
||||
properties,
|
||||
})
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { runPreRequestScript } from "@hoppscotch/js-sandbox"
|
||||
import * as E from "fp-ts/Either"
|
||||
import { runPreRequestScript } from "@hoppscotch/js-sandbox/web"
|
||||
import { Environment } from "@hoppscotch/data"
|
||||
import { cloneDeep } from "lodash-es"
|
||||
|
||||
import {
|
||||
getCurrentEnvironment,
|
||||
getGlobalVariables,
|
||||
} from "~/newstore/environments"
|
||||
import { TestResult } from "@hoppscotch/js-sandbox"
|
||||
|
||||
export const getCombinedEnvVariables = () => ({
|
||||
global: cloneDeep(getGlobalVariables()),
|
||||
@@ -17,4 +20,5 @@ export const getFinalEnvsFromPreRequest = (
|
||||
global: Environment["variables"]
|
||||
selected: Environment["variables"]
|
||||
}
|
||||
) => runPreRequestScript(script, envs)
|
||||
): Promise<E.Either<string, TestResult["envs"]>> =>
|
||||
runPreRequestScript(script, envs)
|
||||
|
||||
@@ -4,6 +4,6 @@
|
||||
export interface Shortcode {
|
||||
id: string
|
||||
request: string
|
||||
properties?: string | null | undefined
|
||||
properties?: string | null
|
||||
createdOn: Date
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
GetUserShortcodesDocument,
|
||||
ShortcodeCreatedDocument,
|
||||
ShortcodeDeletedDocument,
|
||||
ShortcodeUpdatedDocument,
|
||||
} from "../backend/graphql"
|
||||
import { BACKEND_PAGE_SIZE } from "../backend/helpers"
|
||||
import { Shortcode } from "./Shortcode"
|
||||
@@ -25,9 +26,11 @@ export default class ShortcodeListAdapter {
|
||||
|
||||
private shortcodeCreated: Subscription | null
|
||||
private shortcodeRevoked: Subscription | null
|
||||
private shortcodeUpdated: Subscription | null
|
||||
|
||||
private shortcodeCreatedSub: WSubscription | null
|
||||
private shortcodeRevokedSub: WSubscription | null
|
||||
private shortcodeUpdatedSub: WSubscription | null
|
||||
|
||||
constructor(deferInit = false) {
|
||||
this.error$ = new BehaviorSubject<GQLError<string> | null>(null)
|
||||
@@ -39,8 +42,10 @@ export default class ShortcodeListAdapter {
|
||||
this.isDispose = true
|
||||
this.shortcodeCreated = null
|
||||
this.shortcodeRevoked = null
|
||||
this.shortcodeUpdated = null
|
||||
this.shortcodeCreatedSub = null
|
||||
this.shortcodeRevokedSub = null
|
||||
this.shortcodeUpdatedSub = null
|
||||
|
||||
if (!deferInit) this.initialize()
|
||||
}
|
||||
@@ -48,8 +53,10 @@ export default class ShortcodeListAdapter {
|
||||
unsubscribeSubscriptions() {
|
||||
this.shortcodeCreated?.unsubscribe()
|
||||
this.shortcodeRevoked?.unsubscribe()
|
||||
this.shortcodeUpdated?.unsubscribe()
|
||||
this.shortcodeCreatedSub?.unsubscribe()
|
||||
this.shortcodeRevokedSub?.unsubscribe()
|
||||
this.shortcodeUpdatedSub?.unsubscribe()
|
||||
}
|
||||
|
||||
initialize() {
|
||||
@@ -137,6 +144,14 @@ export default class ShortcodeListAdapter {
|
||||
this.shortcodes$.next(newShortcode)
|
||||
}
|
||||
|
||||
private updateSharedRequest(shortcode: Shortcode) {
|
||||
const newShortcode = this.shortcodes$.value.map((oldShortcode) =>
|
||||
oldShortcode.id === shortcode.id ? shortcode : oldShortcode
|
||||
)
|
||||
|
||||
this.shortcodes$.next(newShortcode)
|
||||
}
|
||||
|
||||
private registerSubscriptions() {
|
||||
const [shortcodeCreated$, shortcodeCreatedSub] = runAuthOnlyGQLSubscription(
|
||||
{
|
||||
@@ -169,5 +184,21 @@ export default class ShortcodeListAdapter {
|
||||
|
||||
this.deleteSharedRequest(result.right.myShortcodesRevoked.id)
|
||||
})
|
||||
|
||||
const [shortcodeUpdated$, shortcodeUpdatedSub] = runAuthOnlyGQLSubscription(
|
||||
{
|
||||
query: ShortcodeUpdatedDocument,
|
||||
}
|
||||
)
|
||||
|
||||
this.shortcodeUpdatedSub = shortcodeUpdatedSub
|
||||
this.shortcodeUpdated = shortcodeUpdated$.subscribe((result) => {
|
||||
if (E.isLeft(result)) {
|
||||
console.error(result.left)
|
||||
throw new Error(`Shortcode Update Error ${result.left}`)
|
||||
}
|
||||
|
||||
this.updateSharedRequest(result.right.myShortcodesUpdated)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,107 @@
|
||||
<template>
|
||||
<div class="flex flex-col items-center justify-between p-8">
|
||||
Temporary page for Embed till the feature is ready
|
||||
<div class="flex flex-col flex-1 w-full">
|
||||
<Embeds
|
||||
v-if="tab"
|
||||
v-model:modelTab="tab"
|
||||
:properties="properties"
|
||||
:shared-request-i-d="sharedRequestID"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue"
|
||||
import { watch } from "vue"
|
||||
import { useRoute } from "vue-router"
|
||||
import { useGQLQuery } from "~/composables/graphql"
|
||||
import {
|
||||
ResolveShortcodeDocument,
|
||||
ResolveShortcodeQuery,
|
||||
ResolveShortcodeQueryVariables,
|
||||
} from "~/helpers/backend/graphql"
|
||||
import * as E from "fp-ts/Either"
|
||||
import { onMounted } from "vue"
|
||||
import {
|
||||
getDefaultRESTRequest,
|
||||
safelyExtractRESTRequest,
|
||||
} from "@hoppscotch/data"
|
||||
import { HoppTab } from "~/services/tab"
|
||||
import { HoppRESTDocument } from "~/helpers/rest/document"
|
||||
import { applySetting } from "~/newstore/settings"
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const sharedRequestID = ref("")
|
||||
const invalidLink = ref(false)
|
||||
const properties = ref([])
|
||||
|
||||
const sharedRequestDetails = useGQLQuery<
|
||||
ResolveShortcodeQuery,
|
||||
ResolveShortcodeQueryVariables,
|
||||
""
|
||||
>({
|
||||
query: ResolveShortcodeDocument,
|
||||
variables: {
|
||||
code: route.params.id.toString(),
|
||||
},
|
||||
})
|
||||
|
||||
const tab = ref<HoppTab<HoppRESTDocument>>({
|
||||
id: "0",
|
||||
document: {
|
||||
request: getDefaultRESTRequest(),
|
||||
response: null,
|
||||
isDirty: false,
|
||||
},
|
||||
})
|
||||
|
||||
watch(
|
||||
() => sharedRequestDetails.data,
|
||||
() => {
|
||||
if (sharedRequestDetails.loading) return
|
||||
|
||||
const data = sharedRequestDetails.data
|
||||
|
||||
if (E.isRight(data)) {
|
||||
if (!data.right.shortcode?.request) {
|
||||
invalidLink.value = true
|
||||
return
|
||||
}
|
||||
|
||||
const request: unknown = JSON.parse(
|
||||
data.right.shortcode?.request as string
|
||||
)
|
||||
|
||||
tab.value.document.request = safelyExtractRESTRequest(
|
||||
request,
|
||||
getDefaultRESTRequest()
|
||||
)
|
||||
|
||||
if (data.right.shortcode && data.right.shortcode.properties) {
|
||||
const parsedProperties = JSON.parse(data.right.shortcode.properties)
|
||||
if (parsedProperties.theme === "dark") {
|
||||
applySetting("BG_COLOR", "dark")
|
||||
} else if (parsedProperties.theme === "light") {
|
||||
applySetting("BG_COLOR", "light")
|
||||
} else if (parsedProperties.theme === "auto") {
|
||||
applySetting("BG_COLOR", "system")
|
||||
}
|
||||
properties.value = parsedProperties.options
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
if (typeof route.params.id === "string") {
|
||||
sharedRequestID.value = route.params.id
|
||||
sharedRequestDetails.execute()
|
||||
}
|
||||
invalidLink.value = !sharedRequestID.value
|
||||
})
|
||||
</script>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
layout: empty
|
||||
</route>
|
||||
|
||||
@@ -14,10 +14,12 @@
|
||||
:alt="`${t('empty.profile')}`"
|
||||
:text="`${t('empty.profile')}`"
|
||||
>
|
||||
<HoppButtonPrimary
|
||||
:label="t('auth.login')"
|
||||
@click="invokeAction('modals.login.toggle')"
|
||||
/>
|
||||
<template #body>
|
||||
<HoppButtonPrimary
|
||||
:label="t('auth.login')"
|
||||
@click="invokeAction('modals.login.toggle')"
|
||||
/>
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
<div v-else class="space-y-8">
|
||||
<div
|
||||
|
||||
@@ -5,10 +5,11 @@
|
||||
content-styles="!h-[calc(100%-var(--sidebar-primary-sticky-fold)-1px)] !flex"
|
||||
>
|
||||
<HoppSmartTab
|
||||
v-for="{ target, title } in REALTIME_NAVIGATION"
|
||||
:id="target"
|
||||
:key="target"
|
||||
:label="title"
|
||||
v-for="(navigation, index) in REALTIME_NAVIGATION"
|
||||
:id="navigation.target"
|
||||
:key="index"
|
||||
:label="navigation.title"
|
||||
:icon="navigation.icon"
|
||||
>
|
||||
<RouterView />
|
||||
</HoppSmartTab>
|
||||
@@ -20,6 +21,10 @@ import { watch, ref, computed } from "vue"
|
||||
import { RouterView, useRouter, useRoute } from "vue-router"
|
||||
import { usePageHead } from "~/composables/head"
|
||||
import { useI18n } from "~/composables/i18n"
|
||||
import IconWebsocket from "~icons/hopp/websocket"
|
||||
import IconSocketio from "~icons/hopp/socketio"
|
||||
import IconMqtt from "~icons/hopp/mqtt"
|
||||
import IconSse from "~icons/lucide/satellite-dish"
|
||||
|
||||
const t = useI18n()
|
||||
const router = useRouter()
|
||||
@@ -29,18 +34,22 @@ const REALTIME_NAVIGATION = [
|
||||
{
|
||||
target: "websocket",
|
||||
title: t("tab.websocket"),
|
||||
icon: IconWebsocket,
|
||||
},
|
||||
{
|
||||
target: "sse",
|
||||
title: t("tab.sse"),
|
||||
icon: IconSse,
|
||||
},
|
||||
{
|
||||
target: "socketio",
|
||||
title: t("tab.socketio"),
|
||||
icon: IconSocketio,
|
||||
},
|
||||
{
|
||||
target: "mqtt",
|
||||
title: t("tab.mqtt"),
|
||||
icon: IconMqtt,
|
||||
},
|
||||
] as const
|
||||
|
||||
|
||||
@@ -142,12 +142,14 @@
|
||||
:alt="`${t('empty.subscription')}`"
|
||||
:text="`${t('empty.subscription')}`"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
:label="t('mqtt.new')"
|
||||
filled
|
||||
outline
|
||||
@click="showSubscriptionModal(true)"
|
||||
/>
|
||||
<template #body>
|
||||
<HoppButtonSecondary
|
||||
:label="t('mqtt.new')"
|
||||
filled
|
||||
outline
|
||||
@click="showSubscriptionModal(true)"
|
||||
/>
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
<div v-else>
|
||||
<div
|
||||
|
||||
@@ -197,14 +197,16 @@
|
||||
:alt="`${t('socketio.connection_not_authorized')}`"
|
||||
:text="`${t('socketio.connection_not_authorized')}`"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
outline
|
||||
:label="t('app.documentation')"
|
||||
to="https://docs.hoppscotch.io/documentation/features/authorization"
|
||||
blank
|
||||
:icon="IconExternalLink"
|
||||
reverse
|
||||
/>
|
||||
<template #body>
|
||||
<HoppButtonSecondary
|
||||
outline
|
||||
:label="t('app.documentation')"
|
||||
to="https://docs.hoppscotch.io/documentation/features/authorization"
|
||||
blank
|
||||
:icon="IconExternalLink"
|
||||
reverse
|
||||
/>
|
||||
</template>
|
||||
</HoppSmartPlaceholder>
|
||||
<div
|
||||
v-if="authType === 'Bearer'"
|
||||
|
||||
@@ -163,8 +163,7 @@
|
||||
:src="`/images/states/${colorMode.value}/add_category.svg`"
|
||||
:alt="`${t('empty.protocols')}`"
|
||||
:text="`${t('empty.protocols')}`"
|
||||
>
|
||||
</HoppSmartPlaceholder>
|
||||
/>
|
||||
</HoppSmartTab>
|
||||
</HoppSmartTabs>
|
||||
</template>
|
||||
|
||||
@@ -11,6 +11,7 @@ import { HoppModule } from "~/modules"
|
||||
import { InspectorsPlatformDef } from "./inspectors"
|
||||
import { Service } from "dioc"
|
||||
import { IOPlatformDef } from "./io"
|
||||
import { SpotlightPlatformDef } from "./spotlight"
|
||||
|
||||
export type PlatformDef = {
|
||||
ui?: UIPlatformDef
|
||||
@@ -28,6 +29,7 @@ export type PlatformDef = {
|
||||
}
|
||||
interceptors: InterceptorsPlatformDef
|
||||
additionalInspectors?: InspectorsPlatformDef
|
||||
spotlight?: SpotlightPlatformDef
|
||||
platformFeatureFlags: {
|
||||
exportAsGIST: boolean
|
||||
hasTelemetry: boolean
|
||||
|
||||
10
packages/hoppscotch-common/src/platform/spotlight.ts
Normal file
10
packages/hoppscotch-common/src/platform/spotlight.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Service } from "dioc"
|
||||
import { SpotlightSearcher } from "~/services/spotlight"
|
||||
|
||||
export type SpotlightPlatformDef = {
|
||||
additionalSearchers?: Array<
|
||||
typeof Service<unknown> & { ID: string } & {
|
||||
new (): Service & SpotlightSearcher
|
||||
}
|
||||
>
|
||||
}
|
||||
@@ -26,10 +26,9 @@ export type Banner = {
|
||||
const getBannerWithHighestScore = (list: Banner[]) => {
|
||||
if (list.length === 0) return null
|
||||
else if (list.length === 1) return list[0]
|
||||
else {
|
||||
const highestScore = Math.max(...list.map((banner) => banner.content.score))
|
||||
return list.find((banner) => banner.content.score === highestScore)
|
||||
}
|
||||
|
||||
const highestScore = Math.max(...list.map((banner) => banner.content.score))
|
||||
return list.find((banner) => banner.content.score === highestScore)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -57,6 +57,7 @@ export type SpotlightSearcherResult = {
|
||||
* The keyboard shortcut to trigger the result
|
||||
*/
|
||||
keyboardShortcut?: string[]
|
||||
additionalInfo?: unknown
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
2
packages/hoppscotch-js-sandbox/index.d.ts
vendored
Normal file
2
packages/hoppscotch-js-sandbox/index.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default } from "./dist/types/index.d.ts"
|
||||
export * from "./dist/types/index.d.ts"
|
||||
@@ -1,6 +1,10 @@
|
||||
module.exports = {
|
||||
export default {
|
||||
preset: "ts-jest",
|
||||
testEnvironment: "jsdom",
|
||||
collectCoverage: true,
|
||||
setupFilesAfterEnv: ["./jest.setup.ts"],
|
||||
moduleNameMapper: {
|
||||
"~/(.*)": "<rootDir>/src/$1",
|
||||
"^lodash-es$": "lodash",
|
||||
},
|
||||
}
|
||||
|
||||
2
packages/hoppscotch-js-sandbox/node.d.ts
vendored
Normal file
2
packages/hoppscotch-js-sandbox/node.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default } from "./dist/node.d.ts"
|
||||
export * from "./dist/node.d.ts"
|
||||
@@ -2,16 +2,27 @@
|
||||
"name": "@hoppscotch/js-sandbox",
|
||||
"version": "2.1.0",
|
||||
"description": "JavaScript sandboxes for running external scripts used by Hoppscotch clients",
|
||||
"main": "./lib/index.js",
|
||||
"module": "./lib/index.mjs",
|
||||
"type": "commonjs",
|
||||
"type": "module",
|
||||
"files": [
|
||||
"dist",
|
||||
"index.d.ts"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"require": "./lib/index.js",
|
||||
"default": "./lib/index.mjs"
|
||||
"types": "./dist/types/index.d.ts"
|
||||
},
|
||||
"./web": {
|
||||
"import": "./dist/web.js",
|
||||
"require": "./dist/web.cjs",
|
||||
"types": "./dist/web.d.ts"
|
||||
},
|
||||
"./node": {
|
||||
"import": "./dist/node.js",
|
||||
"require": "./dist/node.cjs",
|
||||
"types": "./dist/node.d.ts"
|
||||
}
|
||||
},
|
||||
"types": "./lib/",
|
||||
"types": "./index.d.ts",
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"pnpm": ">=3"
|
||||
@@ -20,7 +31,7 @@
|
||||
"lint": "eslint --ext .ts,.js --ignore-path .gitignore .",
|
||||
"lintfix": "eslint --fix --ext .ts,.js --ignore-path .gitignore .",
|
||||
"test": "pnpm exec jest",
|
||||
"build": "pnpm exec tsup",
|
||||
"build": "vite build && tsc --emitDeclarationOnly",
|
||||
"clean": "pnpm tsc --build --clean",
|
||||
"postinstall": "pnpm run build",
|
||||
"prepublish": "pnpm run build",
|
||||
@@ -41,10 +52,10 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@hoppscotch/data": "workspace:^",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"fp-ts": "^2.11.10",
|
||||
"lodash": "^4.17.21",
|
||||
"quickjs-emscripten": "^0.15.0",
|
||||
"tsup": "^5.12.5"
|
||||
"lodash-es": "^4.17.21"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@digitak/esrun": "^3.1.2",
|
||||
@@ -61,6 +72,7 @@
|
||||
"jest": "^27.5.1",
|
||||
"prettier": "^2.8.4",
|
||||
"ts-jest": "^27.1.4",
|
||||
"typescript": "^4.6.3"
|
||||
"typescript": "^4.6.3",
|
||||
"vite": "^5.0.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { execPreRequestScript } from "../preRequest"
|
||||
import { runPreRequestScript } from "~/pre-request/node-vm"
|
||||
import "@relmify/jest-fp-ts"
|
||||
|
||||
describe("execPreRequestScript", () => {
|
||||
test("returns the updated envirionment properly", () => {
|
||||
return expect(
|
||||
execPreRequestScript(
|
||||
runPreRequestScript(
|
||||
`
|
||||
pw.env.set("bob", "newbob")
|
||||
`,
|
||||
@@ -27,7 +27,7 @@ describe("execPreRequestScript", () => {
|
||||
|
||||
test("fails if the key is not a string", () => {
|
||||
return expect(
|
||||
execPreRequestScript(
|
||||
runPreRequestScript(
|
||||
`
|
||||
pw.env.set(10, "newbob")
|
||||
`,
|
||||
@@ -44,7 +44,7 @@ describe("execPreRequestScript", () => {
|
||||
|
||||
test("fails if the value is not a string", () => {
|
||||
return expect(
|
||||
execPreRequestScript(
|
||||
runPreRequestScript(
|
||||
`
|
||||
pw.env.set("bob", 10)
|
||||
`,
|
||||
@@ -61,7 +61,7 @@ describe("execPreRequestScript", () => {
|
||||
|
||||
test("fails for invalid syntax", () => {
|
||||
return expect(
|
||||
execPreRequestScript(
|
||||
runPreRequestScript(
|
||||
`
|
||||
pw.env.set("bob",
|
||||
`,
|
||||
@@ -78,7 +78,7 @@ describe("execPreRequestScript", () => {
|
||||
|
||||
test("creates new env variable if doesn't exist", () => {
|
||||
return expect(
|
||||
execPreRequestScript(
|
||||
runPreRequestScript(
|
||||
`
|
||||
pw.env.set("foo", "bar")
|
||||
`,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import "@relmify/jest-fp-ts"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { execTestScript, TestResponse, TestResult } from "../../../test-runner"
|
||||
|
||||
import "@relmify/jest-fp-ts"
|
||||
import { runTestScript } from "~/test-runner/node-vm"
|
||||
import { TestResponse, TestResult } from "~/types"
|
||||
|
||||
const fakeResponse: TestResponse = {
|
||||
status: 200,
|
||||
@@ -12,7 +13,7 @@ const fakeResponse: TestResponse = {
|
||||
|
||||
const func = (script: string, envs: TestResult["envs"]) =>
|
||||
pipe(
|
||||
execTestScript(script, envs, fakeResponse),
|
||||
runTestScript(script, envs, fakeResponse),
|
||||
TE.map((x) => x.tests)
|
||||
)
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import "@relmify/jest-fp-ts"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { execTestScript, TestResponse, TestResult } from "../../../test-runner"
|
||||
|
||||
import "@relmify/jest-fp-ts"
|
||||
import { runTestScript } from "~/test-runner/node-vm"
|
||||
import { TestResponse, TestResult } from "~/types"
|
||||
|
||||
const fakeResponse: TestResponse = {
|
||||
status: 200,
|
||||
@@ -12,7 +13,7 @@ const fakeResponse: TestResponse = {
|
||||
|
||||
const func = (script: string, envs: TestResult["envs"]) =>
|
||||
pipe(
|
||||
execTestScript(script, envs, fakeResponse),
|
||||
runTestScript(script, envs, fakeResponse),
|
||||
TE.map((x) => x.tests)
|
||||
)
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { execTestScript, TestResponse, TestResult } from "../../../test-runner"
|
||||
import { pipe } from "fp-ts/function"
|
||||
|
||||
import { runTestScript } from "~/test-runner/node-vm"
|
||||
import { TestResponse, TestResult } from "~/types"
|
||||
|
||||
const fakeResponse: TestResponse = {
|
||||
status: 200,
|
||||
@@ -10,7 +12,7 @@ const fakeResponse: TestResponse = {
|
||||
|
||||
const func = (script: string, envs: TestResult["envs"]) =>
|
||||
pipe(
|
||||
execTestScript(script, envs, fakeResponse),
|
||||
runTestScript(script, envs, fakeResponse),
|
||||
TE.map((x) => x.tests)
|
||||
)
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { execTestScript, TestResponse, TestResult } from "../../../test-runner"
|
||||
|
||||
import { runTestScript } from "~/test-runner/node-vm"
|
||||
import { TestResponse, TestResult } from "~/types"
|
||||
|
||||
const fakeResponse: TestResponse = {
|
||||
status: 200,
|
||||
@@ -10,13 +12,13 @@ const fakeResponse: TestResponse = {
|
||||
|
||||
const func = (script: string, envs: TestResult["envs"]) =>
|
||||
pipe(
|
||||
execTestScript(script, envs, fakeResponse),
|
||||
runTestScript(script, envs, fakeResponse),
|
||||
TE.map((x) => x.envs)
|
||||
)
|
||||
|
||||
const funcTest = (script: string, envs: TestResult["envs"]) =>
|
||||
pipe(
|
||||
execTestScript(script, envs, fakeResponse),
|
||||
runTestScript(script, envs, fakeResponse),
|
||||
TE.map((x) => x.tests)
|
||||
)
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import "@relmify/jest-fp-ts"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { execTestScript, TestResponse } from "../../../test-runner"
|
||||
import "@relmify/jest-fp-ts"
|
||||
|
||||
import { runTestScript } from "~/test-runner/node-vm"
|
||||
import { TestResponse } from "~/types"
|
||||
|
||||
const fakeResponse: TestResponse = {
|
||||
status: 200,
|
||||
@@ -11,7 +13,7 @@ const fakeResponse: TestResponse = {
|
||||
|
||||
const func = (script: string, res: TestResponse) =>
|
||||
pipe(
|
||||
execTestScript(script, { global: [], selected: [] }, res),
|
||||
runTestScript(script, { global: [], selected: [] }, res),
|
||||
TE.map((x) => x.tests)
|
||||
)
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import "@relmify/jest-fp-ts"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { execTestScript, TestResponse } from "../../../test-runner"
|
||||
import "@relmify/jest-fp-ts"
|
||||
|
||||
import { runTestScript } from "~/test-runner/node-vm"
|
||||
import { TestResponse } from "~/types"
|
||||
|
||||
const fakeResponse: TestResponse = {
|
||||
status: 200,
|
||||
@@ -11,7 +13,7 @@ const fakeResponse: TestResponse = {
|
||||
|
||||
const func = (script: string, res: TestResponse) =>
|
||||
pipe(
|
||||
execTestScript(script, { global: [], selected: [] }, res),
|
||||
runTestScript(script, { global: [], selected: [] }, res),
|
||||
TE.map((x) => x.tests)
|
||||
)
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { execTestScript, TestResponse } from "../../../test-runner"
|
||||
|
||||
import { runTestScript } from "~/test-runner/node-vm"
|
||||
import { TestResponse } from "~/types"
|
||||
|
||||
const fakeResponse: TestResponse = {
|
||||
status: 200,
|
||||
@@ -10,7 +12,7 @@ const fakeResponse: TestResponse = {
|
||||
|
||||
const func = (script: string, res: TestResponse) =>
|
||||
pipe(
|
||||
execTestScript(script, { global: [], selected: [] }, res),
|
||||
runTestScript(script, { global: [], selected: [] }, res),
|
||||
TE.map((x) => x.tests)
|
||||
)
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { execTestScript, TestResponse } from "../../../test-runner"
|
||||
|
||||
import { runTestScript } from "~/test-runner/node-vm"
|
||||
import { TestResponse } from "~/types"
|
||||
|
||||
const fakeResponse: TestResponse = {
|
||||
status: 200,
|
||||
@@ -10,7 +12,7 @@ const fakeResponse: TestResponse = {
|
||||
|
||||
const func = (script: string, res: TestResponse) =>
|
||||
pipe(
|
||||
execTestScript(script, { global: [], selected: [] }, res),
|
||||
runTestScript(script, { global: [], selected: [] }, res),
|
||||
TE.map((x) => x.tests)
|
||||
)
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { execTestScript, TestResponse } from "../../../test-runner"
|
||||
|
||||
import { runTestScript } from "~/test-runner/node-vm"
|
||||
import { TestResponse } from "~/types"
|
||||
|
||||
const fakeResponse: TestResponse = {
|
||||
status: 200,
|
||||
@@ -10,7 +12,7 @@ const fakeResponse: TestResponse = {
|
||||
|
||||
const func = (script: string, res: TestResponse) =>
|
||||
pipe(
|
||||
execTestScript(script, { global: [], selected: [] }, res),
|
||||
runTestScript(script, { global: [], selected: [] }, res),
|
||||
TE.map((x) => x.tests)
|
||||
)
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { execTestScript, TestResponse } from "../../test-runner"
|
||||
|
||||
import { runTestScript } from "~/test-runner/node-vm"
|
||||
import { TestResponse } from "~/types"
|
||||
|
||||
const fakeResponse: TestResponse = {
|
||||
status: 200,
|
||||
@@ -10,7 +12,7 @@ const fakeResponse: TestResponse = {
|
||||
|
||||
const func = (script: string, res: TestResponse) =>
|
||||
pipe(
|
||||
execTestScript(script, { global: [], selected: [] }, res),
|
||||
runTestScript(script, { global: [], selected: [] }, res),
|
||||
TE.map((x) => x.tests)
|
||||
)
|
||||
|
||||
|
||||
@@ -1,40 +1,15 @@
|
||||
import { match } from "fp-ts/lib/Either"
|
||||
import { pipe } from "fp-ts/lib/function"
|
||||
import * as QuickJS from "quickjs-emscripten"
|
||||
import { marshalObjectToVM } from "../utils"
|
||||
import { preventCyclicObjects } from "~/utils"
|
||||
|
||||
let vm: QuickJS.QuickJSVm
|
||||
|
||||
beforeAll(async () => {
|
||||
const qjs = await QuickJS.getQuickJS()
|
||||
vm = qjs.createVm()
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
vm.dispose()
|
||||
})
|
||||
|
||||
describe("marshalObjectToVM", () => {
|
||||
test("successfully marshals simple object into the vm", () => {
|
||||
describe("preventCyclicObjects", () => {
|
||||
test("succeeds with a simple object", () => {
|
||||
const testObj = {
|
||||
a: 1,
|
||||
}
|
||||
|
||||
const objVMHandle: QuickJS.QuickJSHandle | null = pipe(
|
||||
marshalObjectToVM(vm, testObj),
|
||||
match(
|
||||
() => null,
|
||||
(result) => result
|
||||
)
|
||||
)
|
||||
|
||||
expect(objVMHandle).not.toBeNull()
|
||||
expect(vm.dump(objVMHandle!)).toEqual(testObj)
|
||||
|
||||
objVMHandle!.dispose()
|
||||
expect(preventCyclicObjects(testObj)).toBeRight()
|
||||
})
|
||||
|
||||
test("fails marshalling cyclic object into vm", () => {
|
||||
test("fails with a cyclic object", () => {
|
||||
const testObj = {
|
||||
a: 1,
|
||||
b: null as any,
|
||||
@@ -42,14 +17,6 @@ describe("marshalObjectToVM", () => {
|
||||
|
||||
testObj.b = testObj
|
||||
|
||||
const objVMHandle: QuickJS.QuickJSHandle | null = pipe(
|
||||
marshalObjectToVM(vm, testObj),
|
||||
match(
|
||||
() => null,
|
||||
(result) => result
|
||||
)
|
||||
)
|
||||
|
||||
expect(objVMHandle).toBeNull()
|
||||
expect(preventCyclicObjects(testObj)).toBeLeft()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { execPreRequestScript } from "./preRequest"
|
||||
import {
|
||||
execTestScript,
|
||||
TestResponse as _TestResponse,
|
||||
TestDescriptor as _TestDescriptor,
|
||||
TestResult,
|
||||
} from "./test-runner"
|
||||
|
||||
export * from "./test-runner"
|
||||
|
||||
export type TestResponse = _TestResponse
|
||||
export type TestDescriptor = _TestDescriptor
|
||||
export type SandboxTestResult = TestResult & { tests: TestDescriptor }
|
||||
|
||||
/**
|
||||
* Executes a given test script on the test-runner sandbox
|
||||
* @param testScript The string of the script to run
|
||||
* @returns A TaskEither with an error message or a TestDescriptor with the final status
|
||||
*/
|
||||
export const runTestScript = (
|
||||
testScript: string,
|
||||
envs: TestResult["envs"],
|
||||
response: TestResponse
|
||||
) =>
|
||||
pipe(
|
||||
execTestScript(testScript, envs, response),
|
||||
TE.chain((results) =>
|
||||
TE.right(<SandboxTestResult>{
|
||||
envs: results.envs,
|
||||
tests: results.tests[0],
|
||||
})
|
||||
) // execTestScript returns an array of descriptors with a single element (extract that)
|
||||
)
|
||||
|
||||
/**
|
||||
* Executes a given pre-request script on the sandbox
|
||||
* @param preRequestScript The script to run
|
||||
* @param env The environment variables active
|
||||
* @returns A TaskEither with an error message or an array of the final environments with the all the script values applied
|
||||
*/
|
||||
export const runPreRequestScript = execPreRequestScript
|
||||
2
packages/hoppscotch-js-sandbox/src/node.ts
Normal file
2
packages/hoppscotch-js-sandbox/src/node.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./pre-request/node-vm"
|
||||
export * from "./test-runner/node-vm"
|
||||
@@ -0,0 +1,36 @@
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as TE from "fp-ts/lib/TaskEither"
|
||||
import { createContext, runInContext } from "vm"
|
||||
|
||||
import { TestResult } from "~/types"
|
||||
import { getPreRequestScriptMethods } from "~/utils"
|
||||
|
||||
export const runPreRequestScript = (
|
||||
preRequestScript: string,
|
||||
envs: TestResult["envs"]
|
||||
): TE.TaskEither<string, TestResult["envs"]> =>
|
||||
pipe(
|
||||
TE.tryCatch(
|
||||
async () => {
|
||||
return createContext()
|
||||
},
|
||||
(reason) => `Context initialization failed: ${reason}`
|
||||
),
|
||||
TE.chain((context) =>
|
||||
TE.tryCatch(
|
||||
() =>
|
||||
new Promise((resolve) => {
|
||||
const { pw, updatedEnvs } = getPreRequestScriptMethods(envs)
|
||||
|
||||
// Expose pw to the context
|
||||
context.pw = pw
|
||||
|
||||
// Run the pre-request script in the provided context
|
||||
runInContext(preRequestScript, context)
|
||||
|
||||
resolve(updatedEnvs)
|
||||
}),
|
||||
(reason) => `Script execution failed: ${reason}`
|
||||
)
|
||||
)
|
||||
)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user