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"
|
"lint-staged": "12.4.0"
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
|
"overrides": {
|
||||||
|
"vue": "3.3.9"
|
||||||
|
},
|
||||||
"packageExtensions": {
|
"packageExtensions": {
|
||||||
"httpsnippet@^3.0.1": {
|
"httpsnippet@^3.0.1": {
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ module.exports = {
|
|||||||
// The glob patterns Jest uses to detect test files
|
// The glob patterns Jest uses to detect test files
|
||||||
testMatch: [
|
testMatch: [
|
||||||
// "**/__tests__/**/*.[jt]s?(x)",
|
// "**/__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
|
// 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",
|
"debugger": "node debugger.js 9999",
|
||||||
"prepublish": "pnpm exec tsup",
|
"prepublish": "pnpm exec tsup",
|
||||||
"prettier-format": "prettier --config .prettierrc 'src/**/*.ts' --write",
|
"prettier-format": "prettier --config .prettierrc 'src/**/*.ts' --write",
|
||||||
|
"test": "pnpm run build && jest && rm -rf dist",
|
||||||
"do-typecheck": "pnpm exec tsc --noEmit",
|
"do-typecheck": "pnpm exec tsc --noEmit",
|
||||||
"test": "pnpm run build && jest && rm -rf dist"
|
"do-test": "pnpm test"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"cli",
|
"cli",
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ describe("Test 'hopp test <file>' command:", () => {
|
|||||||
|
|
||||||
expect(out).toBe<HoppErrorCode>("UNKNOWN_ERROR");
|
expect(out).toBe<HoppErrorCode>("UNKNOWN_ERROR");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Malformed collection file.", async () => {
|
test("Malformed collection file.", async () => {
|
||||||
const cmd = `node ./bin/hopp test ${getTestJsonFilePath(
|
const cmd = `node ./bin/hopp test ${getTestJsonFilePath(
|
||||||
"malformed-collection2.json"
|
"malformed-collection2.json"
|
||||||
@@ -106,7 +106,7 @@ describe("Test 'hopp test <file> --env <file>' command:", () => {
|
|||||||
const TESTS_PATH = getTestJsonFilePath("env-flag-tests.json");
|
const TESTS_PATH = getTestJsonFilePath("env-flag-tests.json");
|
||||||
const ENV_PATH = getTestJsonFilePath("env-flag-envs.json");
|
const ENV_PATH = getTestJsonFilePath("env-flag-envs.json");
|
||||||
const cmd = `node ./bin/hopp test ${TESTS_PATH} --env ${ENV_PATH}`;
|
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();
|
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 cmd = `${VALID_TEST_CMD} --delay 'NaN'`;
|
||||||
const { stderr } = await execAsync(cmd);
|
const { stderr } = await execAsync(cmd);
|
||||||
const out = getErrorCode(stderr);
|
const out = getErrorCode(stderr);
|
||||||
console.log("invalid value thing", out)
|
|
||||||
expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
|
expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"URL": "https://echo.hoppscotch.io",
|
"URL": "https://echo.hoppscotch.io",
|
||||||
"HOST": "echo.hoppscotch.io",
|
"HOST": "echo.hoppscotch.io",
|
||||||
"X-COUNTRY": "IN",
|
|
||||||
"BODY_VALUE": "body_value",
|
"BODY_VALUE": "body_value",
|
||||||
"BODY_KEY": "body_key"
|
"BODY_KEY": "body_key"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"method": "POST",
|
"method": "POST",
|
||||||
"auth": { "authType": "none", "authActive": true },
|
"auth": { "authType": "none", "authActive": true },
|
||||||
"preRequestScript": "",
|
"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": {
|
"body": {
|
||||||
"contentType": "application/json",
|
"contentType": "application/json",
|
||||||
"body": "{\n \"<<BODY_KEY>>\":\"<<BODY_VALUE>>\"\n}"
|
"body": "{\n \"<<BODY_KEY>>\":\"<<BODY_VALUE>>\"\n}"
|
||||||
|
|||||||
@@ -6,23 +6,24 @@ import {
|
|||||||
parseTemplateString,
|
parseTemplateString,
|
||||||
parseTemplateStringE,
|
parseTemplateStringE,
|
||||||
} from "@hoppscotch/data";
|
} from "@hoppscotch/data";
|
||||||
import { runPreRequestScript } from "@hoppscotch/js-sandbox";
|
import { runPreRequestScript } from "@hoppscotch/js-sandbox/node";
|
||||||
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 * as A from "fp-ts/Array";
|
import * as A from "fp-ts/Array";
|
||||||
|
import * as E from "fp-ts/Either";
|
||||||
import * as O from "fp-ts/Option";
|
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 * as S from "fp-ts/string";
|
||||||
import qs from "qs";
|
import qs from "qs";
|
||||||
|
|
||||||
import { EffectiveHoppRESTRequest } from "../interfaces/request";
|
import { EffectiveHoppRESTRequest } from "../interfaces/request";
|
||||||
import { error, HoppCLIError } from "../types/errors";
|
import { HoppCLIError, error } from "../types/errors";
|
||||||
import { HoppEnvs } from "../types/request";
|
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 { 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
|
* Runs pre-request-script runner over given request which extracts set ENVs and
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
import { HoppRESTRequest } from "@hoppscotch/data";
|
import { HoppRESTRequest } from "@hoppscotch/data";
|
||||||
import { execTestScript, TestDescriptor } from "@hoppscotch/js-sandbox";
|
import { TestDescriptor } from "@hoppscotch/js-sandbox";
|
||||||
import { hrtime } from "process";
|
import { runTestScript } from "@hoppscotch/js-sandbox/node";
|
||||||
import { flow, pipe } from "fp-ts/function";
|
|
||||||
import * as RA from "fp-ts/ReadonlyArray";
|
|
||||||
import * as A from "fp-ts/Array";
|
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 T from "fp-ts/Task";
|
||||||
|
import * as TE from "fp-ts/TaskEither";
|
||||||
|
import { flow, pipe } from "fp-ts/function";
|
||||||
|
import { hrtime } from "process";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
RequestRunnerResponse,
|
RequestRunnerResponse,
|
||||||
TestReport,
|
TestReport,
|
||||||
TestScriptParams,
|
TestScriptParams,
|
||||||
} from "../interfaces/response";
|
} from "../interfaces/response";
|
||||||
import { error, HoppCLIError } from "../types/errors";
|
import { HoppCLIError, error } from "../types/errors";
|
||||||
import { HoppEnvs } from "../types/request";
|
import { HoppEnvs } from "../types/request";
|
||||||
import { ExpectResult, TestMetrics, TestRunnerRes } from "../types/response";
|
import { ExpectResult, TestMetrics, TestRunnerRes } from "../types/response";
|
||||||
import { getDurationInSeconds } from "./getters";
|
import { getDurationInSeconds } from "./getters";
|
||||||
@@ -36,7 +38,7 @@ export const testRunner = (
|
|||||||
pipe(
|
pipe(
|
||||||
TE.of(testScriptData),
|
TE.of(testScriptData),
|
||||||
TE.chain(({ testScript, response, envs }) =>
|
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",
|
"keyboard_shortcuts": "Keyboard shortcuts",
|
||||||
"name": "Hoppscotch",
|
"name": "Hoppscotch",
|
||||||
"new_version_found": "New version found. Refresh to update.",
|
"new_version_found": "New version found. Refresh to update.",
|
||||||
|
"open_in_hoppscotch": "Open in Hoppscotch",
|
||||||
"options": "Options",
|
"options": "Options",
|
||||||
"proxy_privacy_policy": "Proxy privacy policy",
|
"proxy_privacy_policy": "Proxy privacy policy",
|
||||||
"reload": "Reload",
|
"reload": "Reload",
|
||||||
@@ -431,8 +432,9 @@
|
|||||||
"close_unsaved_tab": "You have unsaved changes",
|
"close_unsaved_tab": "You have unsaved changes",
|
||||||
"collections": "Collections",
|
"collections": "Collections",
|
||||||
"confirm": "Confirm",
|
"confirm": "Confirm",
|
||||||
|
"customize_request": "Customize Request",
|
||||||
"edit_request": "Edit Request",
|
"edit_request": "Edit Request",
|
||||||
"share_request":"Share Request",
|
"share_request": "Share Request",
|
||||||
"import_export": "Import / Export"
|
"import_export": "Import / Export"
|
||||||
},
|
},
|
||||||
"mqtt": {
|
"mqtt": {
|
||||||
@@ -621,29 +623,30 @@
|
|||||||
"additional": "Additional Settings",
|
"additional": "Additional Settings",
|
||||||
"verify_email": "Verify email"
|
"verify_email": "Verify email"
|
||||||
},
|
},
|
||||||
"shared_requests":{
|
"shared_requests": {
|
||||||
"button":"Button",
|
"button": "Button",
|
||||||
"button_info": "Create a 'Run in Hoppscotch' button for your website, blog or a README.",
|
"button_info": "Create a 'Run in Hoppscotch' button for your website, blog or a README.",
|
||||||
"customize": "Customize",
|
"customize": "Customize",
|
||||||
"creating_widget": "Creating widget",
|
"creating_widget": "Creating widget",
|
||||||
"copy_html": "Copy HTML",
|
"copy_html": "Copy HTML",
|
||||||
"copy_link": "Copy Link",
|
"copy_link": "Copy Link",
|
||||||
"copy_markdown": "Copy Markdown",
|
"copy_markdown": "Copy Markdown",
|
||||||
"deleted":"Shared request deleted",
|
"deleted": "Shared request deleted",
|
||||||
"description": "Select a widget, you can change and customize this later",
|
"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.",
|
"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.",
|
"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",
|
"open_new_tab": "Open in new tab",
|
||||||
"preview":"Preview",
|
"preview": "Preview",
|
||||||
"run_in_hoppscotch":"Run in Hoppscotch",
|
"run_in_hoppscotch": "Run in Hoppscotch",
|
||||||
"theme":{
|
"theme": {
|
||||||
"dark":"Dark",
|
"dark": "Dark",
|
||||||
"light":"Light",
|
"light": "Light",
|
||||||
"system" :"System",
|
"system": "System",
|
||||||
"title":"Theme"
|
"title": "Theme"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"shortcut": {
|
"shortcut": {
|
||||||
@@ -689,7 +692,7 @@
|
|||||||
"save_to_collections": "Save to Collections",
|
"save_to_collections": "Save to Collections",
|
||||||
"send_request": "Send Request",
|
"send_request": "Send Request",
|
||||||
"show_code": "Generate code snippet",
|
"show_code": "Generate code snippet",
|
||||||
"share_request":"Share Request",
|
"share_request": "Share Request",
|
||||||
"title": "Request"
|
"title": "Request"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ declare module 'vue' {
|
|||||||
CollectionsTeamCollections: typeof import('./components/collections/TeamCollections.vue')['default']
|
CollectionsTeamCollections: typeof import('./components/collections/TeamCollections.vue')['default']
|
||||||
CookiesAllModal: typeof import('./components/cookies/AllModal.vue')['default']
|
CookiesAllModal: typeof import('./components/cookies/AllModal.vue')['default']
|
||||||
CookiesEditCookie: typeof import('./components/cookies/EditCookie.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']
|
Environments: typeof import('./components/environments/index.vue')['default']
|
||||||
EnvironmentsAdd: typeof import('./components/environments/Add.vue')['default']
|
EnvironmentsAdd: typeof import('./components/environments/Add.vue')['default']
|
||||||
EnvironmentsImportExport: typeof import('./components/environments/ImportExport.vue')['default']
|
EnvironmentsImportExport: typeof import('./components/environments/ImportExport.vue')['default']
|
||||||
@@ -163,6 +164,12 @@ declare module 'vue' {
|
|||||||
IconLucideUsers: typeof import('~icons/lucide/users')['default']
|
IconLucideUsers: typeof import('~icons/lucide/users')['default']
|
||||||
IconLucideVerified: typeof import('~icons/lucide/verified')['default']
|
IconLucideVerified: typeof import('~icons/lucide/verified')['default']
|
||||||
IconLucideX: typeof import('~icons/lucide/x')['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']
|
InterceptorsErrorPlaceholder: typeof import('./components/interceptors/ErrorPlaceholder.vue')['default']
|
||||||
InterceptorsExtensionSubtitle: typeof import('./components/interceptors/ExtensionSubtitle.vue')['default']
|
InterceptorsExtensionSubtitle: typeof import('./components/interceptors/ExtensionSubtitle.vue')['default']
|
||||||
LensesHeadersRenderer: typeof import('./components/lenses/HeadersRenderer.vue')['default']
|
LensesHeadersRenderer: typeof import('./components/lenses/HeadersRenderer.vue')['default']
|
||||||
|
|||||||
@@ -305,10 +305,9 @@ watch(isOnline, () => {
|
|||||||
if (!isOnline.value) {
|
if (!isOnline.value) {
|
||||||
bannerID = banner.showBanner(offlineBanner)
|
bannerID = banner.showBanner(offlineBanner)
|
||||||
return
|
return
|
||||||
} else {
|
}
|
||||||
if (banner.content && bannerID) {
|
if (banner.content && bannerID) {
|
||||||
banner.removeBanner(bannerID)
|
banner.removeBanner(bannerID)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -17,9 +17,10 @@
|
|||||||
v-if="isEmpty(shortcutsResults)"
|
v-if="isEmpty(shortcutsResults)"
|
||||||
:text="`${t('state.nothing_found')} ‟${filterText}”`"
|
: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>
|
</HoppSmartPlaceholder>
|
||||||
|
|
||||||
<details
|
<details
|
||||||
v-for="(sectionResults, sectionTitle) in shortcutsResults"
|
v-for="(sectionResults, sectionTitle) in shortcutsResults"
|
||||||
v-else
|
v-else
|
||||||
|
|||||||
@@ -49,13 +49,15 @@
|
|||||||
:text="`${t('state.nothing_found')} ‟${search}”`"
|
:text="`${t('state.nothing_found')} ‟${search}”`"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<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>
|
</template>
|
||||||
<HoppButtonSecondary
|
|
||||||
:label="t('action.clear')"
|
|
||||||
outline
|
|
||||||
@click="search = ''"
|
|
||||||
/>
|
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -112,6 +114,7 @@ import {
|
|||||||
WorkspaceSpotlightSearcherService,
|
WorkspaceSpotlightSearcherService,
|
||||||
} from "~/services/spotlight/searchers/workspace.searcher"
|
} from "~/services/spotlight/searchers/workspace.searcher"
|
||||||
import { InterceptorSpotlightSearcherService } from "~/services/spotlight/searchers/interceptor.searcher"
|
import { InterceptorSpotlightSearcherService } from "~/services/spotlight/searchers/interceptor.searcher"
|
||||||
|
import { platform } from "~/platform"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
@@ -141,6 +144,10 @@ useService(WorkspaceSpotlightSearcherService)
|
|||||||
useService(SwitchWorkspaceSpotlightSearcherService)
|
useService(SwitchWorkspaceSpotlightSearcherService)
|
||||||
useService(InterceptorSpotlightSearcherService)
|
useService(InterceptorSpotlightSearcherService)
|
||||||
|
|
||||||
|
platform.spotlight?.additionalSearchers?.forEach((searcher) =>
|
||||||
|
useService(searcher)
|
||||||
|
)
|
||||||
|
|
||||||
const search = ref("")
|
const search = ref("")
|
||||||
|
|
||||||
const searchSession = ref<SpotlightSearchState>()
|
const searchSession = ref<SpotlightSearchState>()
|
||||||
|
|||||||
@@ -254,7 +254,7 @@
|
|||||||
:text="`${t('state.nothing_found')} ‟${filterText}”`"
|
:text="`${t('state.nothing_found')} ‟${filterText}”`"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<icon-lucide-search class="svg-icons pb-2 opacity-75" />
|
<icon-lucide-search class="svg-icons opacity-75" />
|
||||||
</template>
|
</template>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
<HoppSmartPlaceholder
|
<HoppSmartPlaceholder
|
||||||
@@ -263,27 +263,29 @@
|
|||||||
:alt="`${t('empty.collections')}`"
|
:alt="`${t('empty.collections')}`"
|
||||||
:text="t('empty.collections')"
|
:text="t('empty.collections')"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col items-center space-y-4">
|
<template #body>
|
||||||
<span class="text-center text-secondaryLight">
|
<div class="flex flex-col items-center space-y-4">
|
||||||
{{ t("collection.import_or_create") }}
|
<span class="text-center text-secondaryLight">
|
||||||
</span>
|
{{ t("collection.import_or_create") }}
|
||||||
<div class="flex flex-col items-stretch gap-4">
|
</span>
|
||||||
<HoppButtonPrimary
|
<div class="flex flex-col items-stretch gap-4">
|
||||||
:icon="IconImport"
|
<HoppButtonPrimary
|
||||||
:label="t('import.title')"
|
:icon="IconImport"
|
||||||
filled
|
:label="t('import.title')"
|
||||||
outline
|
filled
|
||||||
@click="emit('display-modal-import-export')"
|
outline
|
||||||
/>
|
@click="emit('display-modal-import-export')"
|
||||||
<HoppButtonSecondary
|
/>
|
||||||
:icon="IconPlus"
|
<HoppButtonSecondary
|
||||||
:label="t('add.new')"
|
:icon="IconPlus"
|
||||||
filled
|
:label="t('add.new')"
|
||||||
outline
|
filled
|
||||||
@click="emit('display-modal-add')"
|
outline
|
||||||
/>
|
@click="emit('display-modal-add')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
<HoppSmartPlaceholder
|
<HoppSmartPlaceholder
|
||||||
v-else-if="node.data.type === 'collections'"
|
v-else-if="node.data.type === 'collections'"
|
||||||
@@ -291,18 +293,20 @@
|
|||||||
:alt="`${t('empty.collections')}`"
|
:alt="`${t('empty.collections')}`"
|
||||||
:text="t('empty.collections')"
|
:text="t('empty.collections')"
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<template #body>
|
||||||
:label="t('add.new')"
|
<HoppButtonSecondary
|
||||||
filled
|
:label="t('add.new')"
|
||||||
outline
|
filled
|
||||||
@click="
|
outline
|
||||||
node.data.type === 'collections' &&
|
@click="
|
||||||
emit('add-folder', {
|
node.data.type === 'collections' &&
|
||||||
path: node.id,
|
emit('add-folder', {
|
||||||
folder: node.data.data.data,
|
path: node.id,
|
||||||
})
|
folder: node.data.data.data,
|
||||||
"
|
})
|
||||||
/>
|
"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
<HoppSmartPlaceholder
|
<HoppSmartPlaceholder
|
||||||
v-else-if="node.data.type === 'folders'"
|
v-else-if="node.data.type === 'folders'"
|
||||||
|
|||||||
@@ -274,33 +274,37 @@
|
|||||||
:text="t('empty.collections')"
|
:text="t('empty.collections')"
|
||||||
@drop.stop
|
@drop.stop
|
||||||
>
|
>
|
||||||
<div class="flex flex-col items-center space-y-4">
|
<template #body>
|
||||||
<span class="text-center text-secondaryLight">
|
<div class="flex flex-col items-center space-y-4">
|
||||||
{{ t("collection.import_or_create") }}
|
<span class="text-center text-secondaryLight">
|
||||||
</span>
|
{{ t("collection.import_or_create") }}
|
||||||
<div class="flex flex-col items-stretch gap-4">
|
</span>
|
||||||
<HoppButtonPrimary
|
<div class="flex flex-col items-stretch gap-4">
|
||||||
:icon="IconImport"
|
<HoppButtonPrimary
|
||||||
:label="t('import.title')"
|
:icon="IconImport"
|
||||||
filled
|
:label="t('import.title')"
|
||||||
outline
|
filled
|
||||||
:disabled="hasNoTeamAccess"
|
outline
|
||||||
:title="hasNoTeamAccess ? t('team.no_access') : ''"
|
:disabled="hasNoTeamAccess"
|
||||||
@click="
|
:title="hasNoTeamAccess ? t('team.no_access') : ''"
|
||||||
hasNoTeamAccess ? null : emit('display-modal-import-export')
|
@click="
|
||||||
"
|
hasNoTeamAccess
|
||||||
/>
|
? null
|
||||||
<HoppButtonSecondary
|
: emit('display-modal-import-export')
|
||||||
:icon="IconPlus"
|
"
|
||||||
:label="t('add.new')"
|
/>
|
||||||
filled
|
<HoppButtonSecondary
|
||||||
outline
|
:icon="IconPlus"
|
||||||
:disabled="hasNoTeamAccess"
|
:label="t('add.new')"
|
||||||
:title="hasNoTeamAccess ? t('team.no_access') : ''"
|
filled
|
||||||
@click="hasNoTeamAccess ? null : emit('display-modal-add')"
|
outline
|
||||||
/>
|
:disabled="hasNoTeamAccess"
|
||||||
|
:title="hasNoTeamAccess ? t('team.no_access') : ''"
|
||||||
|
@click="hasNoTeamAccess ? null : emit('display-modal-add')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
<HoppSmartPlaceholder
|
<HoppSmartPlaceholder
|
||||||
v-else-if="node.data.type === 'collections'"
|
v-else-if="node.data.type === 'collections'"
|
||||||
@@ -309,18 +313,20 @@
|
|||||||
:text="t('empty.collections')"
|
:text="t('empty.collections')"
|
||||||
@drop.stop
|
@drop.stop
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<template #body>
|
||||||
:label="t('add.new')"
|
<HoppButtonSecondary
|
||||||
filled
|
:label="t('add.new')"
|
||||||
outline
|
filled
|
||||||
@click="
|
outline
|
||||||
node.data.type === 'collections' &&
|
@click="
|
||||||
emit('add-folder', {
|
node.data.type === 'collections' &&
|
||||||
path: node.id,
|
emit('add-folder', {
|
||||||
folder: node.data.data.data,
|
path: node.id,
|
||||||
})
|
folder: node.data.data.data,
|
||||||
"
|
})
|
||||||
/>
|
"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
<HoppSmartPlaceholder
|
<HoppSmartPlaceholder
|
||||||
v-else-if="node.data.type === 'folders'"
|
v-else-if="node.data.type === 'folders'"
|
||||||
|
|||||||
@@ -180,16 +180,18 @@
|
|||||||
:alt="`${t('empty.collection')}`"
|
:alt="`${t('empty.collection')}`"
|
||||||
:text="t('empty.collection')"
|
:text="t('empty.collection')"
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<template #body>
|
||||||
:label="t('add.new')"
|
<HoppButtonSecondary
|
||||||
filled
|
:label="t('add.new')"
|
||||||
outline
|
filled
|
||||||
@click="
|
outline
|
||||||
emit('add-folder', {
|
@click="
|
||||||
path: `${collectionIndex}`,
|
emit('add-folder', {
|
||||||
})
|
path: `${collectionIndex}`,
|
||||||
"
|
})
|
||||||
/>
|
"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -176,8 +176,7 @@
|
|||||||
:src="`/images/states/${colorMode.value}/pack.svg`"
|
:src="`/images/states/${colorMode.value}/pack.svg`"
|
||||||
:alt="`${t('empty.folder')}`"
|
:alt="`${t('empty.folder')}`"
|
||||||
:text="t('empty.folder')"
|
:text="t('empty.folder')"
|
||||||
>
|
/>
|
||||||
</HoppSmartPlaceholder>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<HoppSmartConfirmModal
|
<HoppSmartConfirmModal
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
type="search"
|
type="search"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
:placeholder="t('action.search')"
|
: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
|
<div
|
||||||
class="flex flex-1 flex-shrink-0 justify-between border-y border-dividerLight bg-primary"
|
class="flex flex-1 flex-shrink-0 justify-between border-y border-dividerLight bg-primary"
|
||||||
@@ -66,34 +66,36 @@
|
|||||||
:alt="`${t('empty.collections')}`"
|
:alt="`${t('empty.collections')}`"
|
||||||
:text="t('empty.collections')"
|
:text="t('empty.collections')"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col items-center space-y-4">
|
<template #body>
|
||||||
<span class="text-center text-secondaryLight">
|
<div class="flex flex-col items-center space-y-4">
|
||||||
{{ t("collection.import_or_create") }}
|
<span class="text-center text-secondaryLight">
|
||||||
</span>
|
{{ t("collection.import_or_create") }}
|
||||||
<div class="flex flex-col items-stretch gap-4">
|
</span>
|
||||||
<HoppButtonPrimary
|
<div class="flex flex-col items-stretch gap-4">
|
||||||
:icon="IconImport"
|
<HoppButtonPrimary
|
||||||
:label="t('import.title')"
|
:icon="IconImport"
|
||||||
filled
|
:label="t('import.title')"
|
||||||
outline
|
filled
|
||||||
@click="displayModalImportExport(true)"
|
outline
|
||||||
/>
|
@click="displayModalImportExport(true)"
|
||||||
<HoppButtonSecondary
|
/>
|
||||||
:label="t('add.new')"
|
<HoppButtonSecondary
|
||||||
filled
|
:label="t('add.new')"
|
||||||
outline
|
filled
|
||||||
:icon="IconPlus"
|
outline
|
||||||
@click="displayModalAdd(true)"
|
:icon="IconPlus"
|
||||||
/>
|
@click="displayModalAdd(true)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
<HoppSmartPlaceholder
|
<HoppSmartPlaceholder
|
||||||
v-if="!(filteredCollections.length !== 0 || collections.length === 0)"
|
v-if="!(filteredCollections.length !== 0 || collections.length === 0)"
|
||||||
:text="`${t('state.nothing_found')} ‟${filterText}”`"
|
:text="`${t('state.nothing_found')} ‟${filterText}”`"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<icon-lucide-search class="svg-icons pb-2 opacity-75" />
|
<icon-lucide-search class="svg-icons opacity-75" />
|
||||||
</template>
|
</template>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
<CollectionsGraphqlAdd
|
<CollectionsGraphqlAdd
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
v-model="filterTexts"
|
v-model="filterTexts"
|
||||||
type="search"
|
type="search"
|
||||||
autocomplete="off"
|
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')"
|
:placeholder="t('action.search')"
|
||||||
:disabled="collectionsType.type === 'team-collections'"
|
:disabled="collectionsType.type === 'team-collections'"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -11,7 +11,9 @@
|
|||||||
v-if="!currentInterceptorSupportsCookies"
|
v-if="!currentInterceptorSupportsCookies"
|
||||||
:text="t('cookies.modal.interceptor_no_support')"
|
: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>
|
</HoppSmartPlaceholder>
|
||||||
<div v-else class="flex flex-col">
|
<div v-else class="flex flex-col">
|
||||||
<div
|
<div
|
||||||
@@ -38,8 +40,7 @@
|
|||||||
:alt="`${t('cookies.modal.empty_domains')}`"
|
:alt="`${t('cookies.modal.empty_domains')}`"
|
||||||
:text="t('cookies.modal.empty_domains')"
|
:text="t('cookies.modal.empty_domains')"
|
||||||
class="mt-6"
|
class="mt-6"
|
||||||
>
|
/>
|
||||||
</HoppSmartPlaceholder>
|
|
||||||
<div
|
<div
|
||||||
v-for="[domain, entries] in workingCookieJar.entries()"
|
v-for="[domain, entries] in workingCookieJar.entries()"
|
||||||
v-else
|
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"
|
v-if="myEnvironments.length === 0"
|
||||||
class="flex flex-col items-center justify-center text-secondaryLight"
|
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
||||||
>
|
:alt="`${t('empty.environments')}`"
|
||||||
<img
|
:text="t('empty.environments')"
|
||||||
: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>
|
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
<HoppSmartTab
|
<HoppSmartTab
|
||||||
:id="'team-environments'"
|
:id="'team-environments'"
|
||||||
@@ -140,20 +132,12 @@
|
|||||||
}
|
}
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
<div
|
<HoppSmartPlaceholder
|
||||||
v-if="teamEnvironmentList.length === 0"
|
v-if="teamEnvironmentList.length === 0"
|
||||||
class="flex flex-col items-center justify-center text-secondaryLight"
|
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
||||||
>
|
:alt="`${t('empty.environments')}`"
|
||||||
<img
|
:text="t('empty.environments')"
|
||||||
: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>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="!teamListLoading && teamAdapterError"
|
v-if="!teamListLoading && teamAdapterError"
|
||||||
|
|||||||
@@ -78,11 +78,13 @@
|
|||||||
:alt="`${t('empty.environments')}`"
|
:alt="`${t('empty.environments')}`"
|
||||||
:text="t('empty.environments')"
|
:text="t('empty.environments')"
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<template #body>
|
||||||
:label="`${t('add.new')}`"
|
<HoppButtonSecondary
|
||||||
filled
|
:label="`${t('add.new')}`"
|
||||||
@click="addEnvironmentVariable"
|
filled
|
||||||
/>
|
@click="addEnvironmentVariable"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -38,27 +38,29 @@
|
|||||||
:alt="`${t('empty.environments')}`"
|
:alt="`${t('empty.environments')}`"
|
||||||
:text="t('empty.environments')"
|
:text="t('empty.environments')"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col items-center space-y-4">
|
<template #body>
|
||||||
<span class="text-center text-secondaryLight">
|
<div class="flex flex-col items-center space-y-4">
|
||||||
{{ t("environment.import_or_create") }}
|
<span class="text-center text-secondaryLight">
|
||||||
</span>
|
{{ t("environment.import_or_create") }}
|
||||||
<div class="flex flex-col items-stretch gap-4">
|
</span>
|
||||||
<HoppButtonPrimary
|
<div class="flex flex-col items-stretch gap-4">
|
||||||
:icon="IconImport"
|
<HoppButtonPrimary
|
||||||
:label="t('import.title')"
|
:icon="IconImport"
|
||||||
filled
|
:label="t('import.title')"
|
||||||
outline
|
filled
|
||||||
@click="displayModalImportExport(true)"
|
outline
|
||||||
/>
|
@click="displayModalImportExport(true)"
|
||||||
<HoppButtonSecondary
|
/>
|
||||||
:icon="IconPlus"
|
<HoppButtonSecondary
|
||||||
:label="`${t('add.new')}`"
|
:icon="IconPlus"
|
||||||
filled
|
:label="`${t('add.new')}`"
|
||||||
outline
|
filled
|
||||||
@click="displayModalAdd(true)"
|
outline
|
||||||
/>
|
@click="displayModalAdd(true)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
<EnvironmentsMyDetails
|
<EnvironmentsMyDetails
|
||||||
:show="showModalDetails"
|
:show="showModalDetails"
|
||||||
|
|||||||
@@ -81,18 +81,20 @@
|
|||||||
:alt="`${t('empty.environments')}`"
|
:alt="`${t('empty.environments')}`"
|
||||||
:text="t('empty.environments')"
|
:text="t('empty.environments')"
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<template #body>
|
||||||
v-if="isViewer"
|
<HoppButtonSecondary
|
||||||
disabled
|
v-if="isViewer"
|
||||||
:label="`${t('add.new')}`"
|
disabled
|
||||||
filled
|
:label="`${t('add.new')}`"
|
||||||
/>
|
filled
|
||||||
<HoppButtonSecondary
|
/>
|
||||||
v-else
|
<HoppButtonSecondary
|
||||||
:label="`${t('add.new')}`"
|
v-else
|
||||||
filled
|
:label="`${t('add.new')}`"
|
||||||
@click="addEnvironmentVariable"
|
filled
|
||||||
/>
|
@click="addEnvironmentVariable"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -49,31 +49,33 @@
|
|||||||
:alt="`${t('empty.environments')}`"
|
:alt="`${t('empty.environments')}`"
|
||||||
:text="t('empty.environments')"
|
:text="t('empty.environments')"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col items-center space-y-4">
|
<template #body>
|
||||||
<span class="text-center text-secondaryLight">
|
<div class="flex flex-col items-center space-y-4">
|
||||||
{{ t("environment.import_or_create") }}
|
<span class="text-center text-secondaryLight">
|
||||||
</span>
|
{{ t("environment.import_or_create") }}
|
||||||
<div class="flex flex-col items-stretch gap-4">
|
</span>
|
||||||
<HoppButtonPrimary
|
<div class="flex flex-col items-stretch gap-4">
|
||||||
:icon="IconImport"
|
<HoppButtonPrimary
|
||||||
:label="t('import.title')"
|
:icon="IconImport"
|
||||||
filled
|
:label="t('import.title')"
|
||||||
outline
|
filled
|
||||||
:title="isTeamViewer ? t('team.no_access') : ''"
|
outline
|
||||||
:disabled="isTeamViewer"
|
:title="isTeamViewer ? t('team.no_access') : ''"
|
||||||
@click="isTeamViewer ? null : displayModalImportExport(true)"
|
:disabled="isTeamViewer"
|
||||||
/>
|
@click="isTeamViewer ? null : displayModalImportExport(true)"
|
||||||
<HoppButtonSecondary
|
/>
|
||||||
:label="`${t('add.new')}`"
|
<HoppButtonSecondary
|
||||||
filled
|
:label="`${t('add.new')}`"
|
||||||
outline
|
filled
|
||||||
:icon="IconPlus"
|
outline
|
||||||
:title="isTeamViewer ? t('team.no_access') : ''"
|
:icon="IconPlus"
|
||||||
:disabled="isTeamViewer"
|
:title="isTeamViewer ? t('team.no_access') : ''"
|
||||||
@click="isTeamViewer ? null : displayModalAdd(true)"
|
:disabled="isTeamViewer"
|
||||||
/>
|
@click="isTeamViewer ? null : displayModalAdd(true)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
<div v-else-if="!loading">
|
<div v-else-if="!loading">
|
||||||
<EnvironmentsTeamsEnvironment
|
<EnvironmentsTeamsEnvironment
|
||||||
|
|||||||
@@ -120,14 +120,16 @@
|
|||||||
:alt="`${t('empty.authorization')}`"
|
:alt="`${t('empty.authorization')}`"
|
||||||
:text="t('empty.authorization')"
|
:text="t('empty.authorization')"
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<template #body>
|
||||||
outline
|
<HoppButtonSecondary
|
||||||
:label="t('app.documentation')"
|
outline
|
||||||
to="https://docs.hoppscotch.io/documentation/features/authorization"
|
:label="t('app.documentation')"
|
||||||
blank
|
to="https://docs.hoppscotch.io/documentation/features/authorization"
|
||||||
:icon="IconExternalLink"
|
blank
|
||||||
reverse
|
:icon="IconExternalLink"
|
||||||
/>
|
reverse
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
<div v-else class="flex flex-1 border-b border-dividerLight">
|
<div v-else class="flex flex-1 border-b border-dividerLight">
|
||||||
<div class="w-2/3 border-r border-dividerLight">
|
<div class="w-2/3 border-r border-dividerLight">
|
||||||
|
|||||||
@@ -162,12 +162,14 @@
|
|||||||
:alt="`${t('empty.headers')}`"
|
:alt="`${t('empty.headers')}`"
|
||||||
:text="t('empty.headers')"
|
:text="t('empty.headers')"
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<template #body>
|
||||||
:label="`${t('add.new')}`"
|
<HoppButtonSecondary
|
||||||
filled
|
:label="`${t('add.new')}`"
|
||||||
:icon="IconPlus"
|
filled
|
||||||
@click="addHeader"
|
:icon="IconPlus"
|
||||||
/>
|
@click="addHeader"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -20,8 +20,7 @@
|
|||||||
:src="`/images/states/${colorMode.value}/add_comment.svg`"
|
:src="`/images/states/${colorMode.value}/add_comment.svg`"
|
||||||
:alt="`${t('empty.documentation')}`"
|
:alt="`${t('empty.documentation')}`"
|
||||||
:text="t('empty.documentation')"
|
:text="t('empty.documentation')"
|
||||||
>
|
/>
|
||||||
</HoppSmartPlaceholder>
|
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<div
|
<div
|
||||||
class="sticky top-0 z-10 flex flex-shrink-0 overflow-x-auto bg-primary"
|
class="sticky top-0 z-10 flex flex-shrink-0 overflow-x-auto bg-primary"
|
||||||
@@ -30,7 +29,7 @@
|
|||||||
v-model="graphqlFieldsFilterText"
|
v-model="graphqlFieldsFilterText"
|
||||||
type="search"
|
type="search"
|
||||||
autocomplete="off"
|
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')}`"
|
:placeholder="`${t('action.search')}`"
|
||||||
/>
|
/>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
|
|||||||
@@ -3,13 +3,13 @@
|
|||||||
<div
|
<div
|
||||||
class="sticky top-0 z-10 flex flex-shrink-0 flex-col overflow-x-auto border-b border-dividerLight bg-primary"
|
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">
|
<div class="flex">
|
||||||
<input
|
<input
|
||||||
v-model="filterText"
|
v-model="filterText"
|
||||||
type="search"
|
type="search"
|
||||||
autocomplete="off"
|
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')}`"
|
:placeholder="`${t('action.search')}`"
|
||||||
/>
|
/>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
@@ -114,8 +114,7 @@
|
|||||||
:src="`/images/states/${colorMode.value}/history.svg`"
|
:src="`/images/states/${colorMode.value}/history.svg`"
|
||||||
:alt="`${t('empty.history')}`"
|
:alt="`${t('empty.history')}`"
|
||||||
:text="t('empty.history')"
|
:text="t('empty.history')"
|
||||||
>
|
/>
|
||||||
</HoppSmartPlaceholder>
|
|
||||||
<HoppSmartPlaceholder
|
<HoppSmartPlaceholder
|
||||||
v-else-if="
|
v-else-if="
|
||||||
Object.keys(filteredHistoryGroups).length === 0 ||
|
Object.keys(filteredHistoryGroups).length === 0 ||
|
||||||
@@ -124,18 +123,20 @@
|
|||||||
:text="`${t('state.nothing_found')} ‟${filterText || filterSelection}”`"
|
:text="`${t('state.nothing_found')} ‟${filterText || filterSelection}”`"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<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>
|
</template>
|
||||||
<HoppButtonSecondary
|
|
||||||
:label="t('action.clear')"
|
|
||||||
outline
|
|
||||||
@click="
|
|
||||||
() => {
|
|
||||||
filterText = ''
|
|
||||||
filterSelection = 'ALL'
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
<HoppSmartConfirmModal
|
<HoppSmartConfirmModal
|
||||||
:show="confirmRemove"
|
:show="confirmRemove"
|
||||||
|
|||||||
@@ -119,14 +119,16 @@
|
|||||||
:alt="`${t('empty.authorization')}`"
|
:alt="`${t('empty.authorization')}`"
|
||||||
:text="t('empty.authorization')"
|
:text="t('empty.authorization')"
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<template #body>
|
||||||
outline
|
<HoppButtonSecondary
|
||||||
:label="t('app.documentation')"
|
outline
|
||||||
to="https://docs.hoppscotch.io/documentation/features/authorization"
|
:label="t('app.documentation')"
|
||||||
blank
|
to="https://docs.hoppscotch.io/documentation/features/authorization"
|
||||||
:icon="IconExternalLink"
|
blank
|
||||||
reverse
|
:icon="IconExternalLink"
|
||||||
/>
|
reverse
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
<div v-else class="flex flex-1 border-b border-dividerLight">
|
<div v-else class="flex flex-1 border-b border-dividerLight">
|
||||||
<div class="w-2/3 border-r border-dividerLight">
|
<div class="w-2/3 border-r border-dividerLight">
|
||||||
|
|||||||
@@ -112,14 +112,16 @@
|
|||||||
:alt="`${t('empty.body')}`"
|
:alt="`${t('empty.body')}`"
|
||||||
:text="t('empty.body')"
|
:text="t('empty.body')"
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<template #body>
|
||||||
outline
|
<HoppButtonSecondary
|
||||||
:label="`${t('app.documentation')}`"
|
outline
|
||||||
to="https://docs.hoppscotch.io/documentation/getting-started/rest/uploading-data"
|
:label="`${t('app.documentation')}`"
|
||||||
blank
|
to="https://docs.hoppscotch.io/documentation/getting-started/rest/uploading-data"
|
||||||
:icon="IconExternalLink"
|
blank
|
||||||
reverse
|
:icon="IconExternalLink"
|
||||||
/>
|
reverse
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -158,12 +158,14 @@
|
|||||||
:alt="`${t('empty.body')}`"
|
:alt="`${t('empty.body')}`"
|
||||||
:text="t('empty.body')"
|
:text="t('empty.body')"
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<template #body>
|
||||||
:label="`${t('add.new')}`"
|
<HoppButtonSecondary
|
||||||
filled
|
:label="`${t('add.new')}`"
|
||||||
:icon="IconPlus"
|
filled
|
||||||
@click="addBodyParam"
|
:icon="IconPlus"
|
||||||
/>
|
@click="addBodyParam"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
:text="`${t('state.nothing_found')} ‟${searchQuery}”`"
|
:text="`${t('state.nothing_found')} ‟${searchQuery}”`"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<icon-lucide-search class="svg-icons pb-2 opacity-75" />
|
<icon-lucide-search class="svg-icons opacity-75" />
|
||||||
</template>
|
</template>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -209,12 +209,14 @@
|
|||||||
:alt="`${t('empty.headers')}`"
|
:alt="`${t('empty.headers')}`"
|
||||||
:text="t('empty.headers')"
|
:text="t('empty.headers')"
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<template #body>
|
||||||
filled
|
<HoppButtonSecondary
|
||||||
:label="`${t('add.new')}`"
|
filled
|
||||||
:icon="IconPlus"
|
:label="`${t('add.new')}`"
|
||||||
@click="addHeader"
|
:icon="IconPlus"
|
||||||
/>
|
@click="addHeader"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -157,12 +157,14 @@
|
|||||||
:alt="`${t('empty.parameters')}`"
|
:alt="`${t('empty.parameters')}`"
|
||||||
:text="t('empty.parameters')"
|
:text="t('empty.parameters')"
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<template #body>
|
||||||
:label="`${t('add.new')}`"
|
<HoppButtonSecondary
|
||||||
:icon="IconPlus"
|
:label="`${t('add.new')}`"
|
||||||
filled
|
:icon="IconPlus"
|
||||||
@click="addParam"
|
filled
|
||||||
/>
|
@click="addParam"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -180,6 +180,18 @@
|
|||||||
class="input mb-2 !bg-primaryContrast"
|
class="input mb-2 !bg-primaryContrast"
|
||||||
@keyup.enter="hide()"
|
@keyup.enter="hide()"
|
||||||
/>
|
/>
|
||||||
|
<HoppSmartItem
|
||||||
|
ref="saveRequestAction"
|
||||||
|
:label="`${t('request.save_as')}`"
|
||||||
|
:icon="IconFolderPlus"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
showSaveRequestModal = true
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<hr />
|
||||||
<HoppSmartItem
|
<HoppSmartItem
|
||||||
ref="copyRequestAction"
|
ref="copyRequestAction"
|
||||||
:label="t('request.share_request')"
|
: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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</tippy>
|
</tippy>
|
||||||
|
|||||||
@@ -5,13 +5,18 @@
|
|||||||
render-inactive-tabs
|
render-inactive-tabs
|
||||||
>
|
>
|
||||||
<HoppSmartTab
|
<HoppSmartTab
|
||||||
|
v-if="properties ? properties.includes('parameters') : true"
|
||||||
:id="'params'"
|
:id="'params'"
|
||||||
:label="`${t('tab.parameters')}`"
|
:label="`${t('tab.parameters')}`"
|
||||||
:info="`${newActiveParamsCount$}`"
|
:info="`${newActiveParamsCount$}`"
|
||||||
>
|
>
|
||||||
<HttpParameters v-model="request.params" />
|
<HttpParameters v-model="request.params" />
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
<HoppSmartTab :id="'bodyParams'" :label="`${t('tab.body')}`">
|
<HoppSmartTab
|
||||||
|
v-if="properties ? properties.includes('body') : true"
|
||||||
|
:id="'bodyParams'"
|
||||||
|
:label="`${t('tab.body')}`"
|
||||||
|
>
|
||||||
<HttpBody
|
<HttpBody
|
||||||
v-model:headers="request.headers"
|
v-model:headers="request.headers"
|
||||||
v-model:body="request.body"
|
v-model:body="request.body"
|
||||||
@@ -19,16 +24,22 @@
|
|||||||
/>
|
/>
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
<HoppSmartTab
|
<HoppSmartTab
|
||||||
|
v-if="properties ? properties.includes('headers') : true"
|
||||||
:id="'headers'"
|
:id="'headers'"
|
||||||
:label="`${t('tab.headers')}`"
|
:label="`${t('tab.headers')}`"
|
||||||
:info="`${newActiveHeadersCount$}`"
|
:info="`${newActiveHeadersCount$}`"
|
||||||
>
|
>
|
||||||
<HttpHeaders v-model="request" @change-tab="changeOptionTab" />
|
<HttpHeaders v-model="request" @change-tab="changeOptionTab" />
|
||||||
</HoppSmartTab>
|
</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" />
|
<HttpAuthorization v-model="request.auth" />
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
<HoppSmartTab
|
<HoppSmartTab
|
||||||
|
v-if="properties ? properties.includes('preRequestScript') : true"
|
||||||
:id="'preRequestScript'"
|
:id="'preRequestScript'"
|
||||||
:label="`${t('tab.pre_request_script')}`"
|
:label="`${t('tab.pre_request_script')}`"
|
||||||
:indicator="
|
:indicator="
|
||||||
@@ -40,6 +51,7 @@
|
|||||||
<HttpPreRequestScript v-model="request.preRequestScript" />
|
<HttpPreRequestScript v-model="request.preRequestScript" />
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
<HoppSmartTab
|
<HoppSmartTab
|
||||||
|
v-if="properties ? properties.includes('tests') : true"
|
||||||
:id="'tests'"
|
:id="'tests'"
|
||||||
:label="`${t('tab.tests')}`"
|
:label="`${t('tab.tests')}`"
|
||||||
:indicator="
|
:indicator="
|
||||||
@@ -76,6 +88,7 @@ const props = withDefaults(
|
|||||||
defineProps<{
|
defineProps<{
|
||||||
modelValue: HoppRESTRequest
|
modelValue: HoppRESTRequest
|
||||||
optionTab: RESTOptionTabs
|
optionTab: RESTOptionTabs
|
||||||
|
properties?: string[]
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
optionTab: "params",
|
optionTab: "params",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="relative flex flex-1 flex-col">
|
<div class="relative flex flex-1 flex-col">
|
||||||
<HttpResponseMeta :response="doc.response" />
|
<HttpResponseMeta :response="doc.response" :is-embed="isEmbed" />
|
||||||
<LensesResponseBodyRenderer
|
<LensesResponseBodyRenderer
|
||||||
v-if="!loading && hasResponse"
|
v-if="!loading && hasResponse"
|
||||||
v-model:document="doc"
|
v-model:document="doc"
|
||||||
@@ -15,6 +15,7 @@ import { HoppRESTDocument } from "~/helpers/rest/document"
|
|||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
document: HoppRESTDocument
|
document: HoppRESTDocument
|
||||||
|
isEmbed: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
|||||||
@@ -2,8 +2,20 @@
|
|||||||
<div
|
<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"
|
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" />
|
<AppShortcutsPrompt v-if="response == null && !isEmbed" class="flex-1" />
|
||||||
<div v-else class="flex flex-1 flex-col">
|
|
||||||
|
<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
|
<div
|
||||||
v-if="response.type === 'loading'"
|
v-if="response.type === 'loading'"
|
||||||
class="flex flex-col items-center justify-center"
|
class="flex flex-col items-center justify-center"
|
||||||
@@ -25,7 +37,9 @@
|
|||||||
:text="t('helpers.network_fail')"
|
:text="t('helpers.network_fail')"
|
||||||
large
|
large
|
||||||
>
|
>
|
||||||
<AppInterceptor class="rounded border border-dividerLight p-2" />
|
<template #body>
|
||||||
|
<AppInterceptor class="rounded border border-dividerLight p-2" />
|
||||||
|
</template>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
<HoppSmartPlaceholder
|
<HoppSmartPlaceholder
|
||||||
v-if="response.type === 'script_fail'"
|
v-if="response.type === 'script_fail'"
|
||||||
@@ -35,12 +49,14 @@
|
|||||||
:text="t('helpers.script_fail')"
|
:text="t('helpers.script_fail')"
|
||||||
large
|
large
|
||||||
>
|
>
|
||||||
<div
|
<template #body>
|
||||||
class="mt-2 w-full overflow-auto whitespace-normal rounded bg-primaryLight px-4 py-2 font-mono text-red-400"
|
<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 }}
|
{{ response.error.name }}: {{ response.error.message }}<br />
|
||||||
</div>
|
{{ response.error.stack }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
<div
|
<div
|
||||||
v-if="response.type === 'success' || response.type === 'fail'"
|
v-if="response.type === 'success' || response.type === 'fail'"
|
||||||
@@ -105,6 +121,7 @@ import { getStatusCodeReasonPhrase } from "~/helpers/utils/statusCodes"
|
|||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
import { InspectionService } from "~/services/inspection"
|
import { InspectionService } from "~/services/inspection"
|
||||||
import { RESTTabService } from "~/services/tab/rest"
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
import IconExternalLink from "~icons/lucide/external-link"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
@@ -112,6 +129,7 @@ const tabs = useService(RESTTabService)
|
|||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
response: HoppRESTResponse | null | undefined
|
response: HoppRESTResponse | null | undefined
|
||||||
|
isEmbed?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
<div class="divide-y divide-dividerLight">
|
<div class="divide-y divide-dividerLight">
|
||||||
<div
|
<div
|
||||||
v-if="noEnvSelected && !globalHasAdditions"
|
v-if="noEnvSelected && !globalHasAdditions"
|
||||||
class="flex bg-info p-4 text-secondaryDark"
|
class="flex bg-bannerInfo p-4 text-secondaryDark"
|
||||||
role="alert"
|
role="alert"
|
||||||
>
|
>
|
||||||
<icon-lucide-alert-triangle class="svg-icons mr-4" />
|
<icon-lucide-alert-triangle class="svg-icons mr-4" />
|
||||||
@@ -159,8 +159,7 @@
|
|||||||
:alt="`${t('error.test_script_fail')}`"
|
:alt="`${t('error.test_script_fail')}`"
|
||||||
:heading="t('error.test_script_fail')"
|
:heading="t('error.test_script_fail')"
|
||||||
:text="t('helpers.test_script_fail')"
|
:text="t('helpers.test_script_fail')"
|
||||||
>
|
/>
|
||||||
</HoppSmartPlaceholder>
|
|
||||||
<HoppSmartPlaceholder
|
<HoppSmartPlaceholder
|
||||||
v-else
|
v-else
|
||||||
:src="`/images/states/${colorMode.value}/validation.svg`"
|
:src="`/images/states/${colorMode.value}/validation.svg`"
|
||||||
@@ -168,15 +167,16 @@
|
|||||||
:heading="t('empty.tests')"
|
:heading="t('empty.tests')"
|
||||||
:text="t('helpers.tests')"
|
:text="t('helpers.tests')"
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<template #body>
|
||||||
outline
|
<HoppButtonSecondary
|
||||||
:label="`${t('action.learn_more')}`"
|
outline
|
||||||
to="https://docs.hoppscotch.io/documentation/getting-started/rest/tests"
|
:label="`${t('action.learn_more')}`"
|
||||||
blank
|
to="https://docs.hoppscotch.io/documentation/getting-started/rest/tests"
|
||||||
:icon="IconExternalLink"
|
blank
|
||||||
reverse
|
:icon="IconExternalLink"
|
||||||
class="my-4"
|
reverse
|
||||||
/>
|
/>
|
||||||
|
</template>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
<EnvironmentsMyDetails
|
<EnvironmentsMyDetails
|
||||||
:show="showMyEnvironmentDetailsModal"
|
:show="showMyEnvironmentDetailsModal"
|
||||||
|
|||||||
@@ -149,12 +149,14 @@
|
|||||||
:alt="`${t('empty.body')}`"
|
:alt="`${t('empty.body')}`"
|
||||||
:text="t('empty.body')"
|
:text="t('empty.body')"
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<template #body>
|
||||||
filled
|
<HoppButtonSecondary
|
||||||
:label="`${t('add.new')}`"
|
filled
|
||||||
:icon="IconPlus"
|
:label="`${t('add.new')}`"
|
||||||
@click="addUrlEncodedParam"
|
:icon="IconPlus"
|
||||||
/>
|
@click="addUrlEncodedParam"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,55 +5,57 @@
|
|||||||
:heading="t('error.network_fail')"
|
:heading="t('error.network_fail')"
|
||||||
large
|
large
|
||||||
>
|
>
|
||||||
<div class="my-1 flex flex-col items-center text-secondaryLight">
|
<template #body>
|
||||||
<span>
|
<div class="my-1 flex flex-col items-center text-secondaryLight">
|
||||||
{{ t("error.please_install_extension") }}
|
<span>
|
||||||
</span>
|
{{ t("error.please_install_extension") }}
|
||||||
<span>
|
</span>
|
||||||
{{ t("error.check_how_to_add_origin") }}
|
<span>
|
||||||
<HoppSmartLink
|
{{ t("error.check_how_to_add_origin") }}
|
||||||
blank
|
<HoppSmartLink
|
||||||
to="https://docs.hoppscotch.io/documentation/features/interceptor#browser-extension"
|
blank
|
||||||
class="text-accent hover:text-accentDark"
|
to="https://docs.hoppscotch.io/documentation/features/interceptor#browser-extension"
|
||||||
>
|
class="text-accent hover:text-accentDark"
|
||||||
here
|
>
|
||||||
</HoppSmartLink>
|
here
|
||||||
</span>
|
</HoppSmartLink>
|
||||||
</div>
|
</span>
|
||||||
<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>
|
||||||
</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>
|
</HoppSmartPlaceholder>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else-if="myTeams.length"
|
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">
|
<h2 class="font-bold text-red-500">
|
||||||
{{ t("error.danger_zone") }}
|
{{ t("error.danger_zone") }}
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<div
|
<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">
|
<h2 class="font-bold text-red-500">
|
||||||
{{ t("error.danger_zone") }}
|
{{ t("error.danger_zone") }}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-if="selectedWidget"
|
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">
|
<div v-if="loading" class="px-4 py-2">
|
||||||
{{ t("shared_requests.creating_widget") }}
|
{{ t("shared_requests.creating_widget") }}
|
||||||
@@ -10,17 +10,17 @@
|
|||||||
{{ t("shared_requests.description") }}
|
{{ t("shared_requests.description") }}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col divide-y divide-divider">
|
<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
|
<div
|
||||||
v-for="widget in widgets"
|
v-for="widget in widgets"
|
||||||
:key="widget.value"
|
: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="{
|
:class="{
|
||||||
'!border-accentLight': selectedWidget.value === widget.value,
|
'!border-accentLight': selectedWidget.value === widget.value,
|
||||||
}"
|
}"
|
||||||
@click="selectedWidget = widget"
|
@click="selectedWidget = widget"
|
||||||
>
|
>
|
||||||
<span class="text-md font-bold">
|
<span class="mb-1 font-bold text-secondaryDark">
|
||||||
{{ widget.label }}
|
{{ widget.label }}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-tiny">
|
<span class="text-tiny">
|
||||||
@@ -28,9 +28,13 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col divide-y divide-divider">
|
<div class="flex flex-col items-center justify-center p-4">
|
||||||
<div class="px-4 py-3">{{ t("shared_requests.preview") }}</div>
|
<span
|
||||||
<div class="flex flex-col items-center justify-center px-4 py-10">
|
class="flex justify-center flex-1 mb-2 text-secondaryLight text-tiny"
|
||||||
|
>
|
||||||
|
{{ t("shared_requests.preview") }}
|
||||||
|
</span>
|
||||||
|
<div class="w-full">
|
||||||
<ShareTemplatesEmbeds
|
<ShareTemplatesEmbeds
|
||||||
v-if="selectedWidget.value === 'embed'"
|
v-if="selectedWidget.value === 'embed'"
|
||||||
:endpoint="request?.endpoint"
|
:endpoint="request?.endpoint"
|
||||||
@@ -132,7 +136,7 @@ const embedOption = ref<EmbedOption>({
|
|||||||
{
|
{
|
||||||
value: "authorization",
|
value: "authorization",
|
||||||
label: t("tab.authorization"),
|
label: t("tab.authorization"),
|
||||||
enabled: true,
|
enabled: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
theme: "system",
|
theme: "system",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-if="selectedWidget"
|
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">
|
<div v-if="loading" class="px-4 py-2">
|
||||||
{{ t("shared_requests.creating_widget") }}
|
{{ t("shared_requests.creating_widget") }}
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
<span class="text-secondaryLight">{{ t("state.loading") }}</span>
|
<span class="text-secondaryLight">{{ t("state.loading") }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="flex flex-col divide-y divide-divider">
|
<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
|
<HoppSmartRadioGroup
|
||||||
v-model="selectedWidget.value"
|
v-model="selectedWidget.value"
|
||||||
:radios="widgets"
|
:radios="widgets"
|
||||||
@@ -22,9 +22,9 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col divide-y divide-divider">
|
<div class="flex flex-col divide-y divide-divider">
|
||||||
<div class="flex items-center justify-center px-4 py-8">
|
<div class="flex items-center justify-center px-6 py-4">
|
||||||
<div v-if="selectedWidget.value === 'embed'" class="w-full flex-1">
|
<div v-if="selectedWidget.value === 'embed'" class="w-full">
|
||||||
<div class="flex flex-col pb-8">
|
<div class="flex flex-col pb-4">
|
||||||
<div
|
<div
|
||||||
v-for="option in embedOptions.tabs"
|
v-for="option in embedOptions.tabs"
|
||||||
:key="option.value"
|
:key="option.value"
|
||||||
@@ -36,7 +36,8 @@
|
|||||||
<HoppSmartCheckbox
|
<HoppSmartCheckbox
|
||||||
:on="option.enabled"
|
:on="option.enabled"
|
||||||
@change="removeEmbedOption(option.value)"
|
@change="removeEmbedOption(option.value)"
|
||||||
/>
|
>
|
||||||
|
</HoppSmartCheckbox>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<span>
|
<span>
|
||||||
@@ -49,13 +50,11 @@
|
|||||||
theme="popover"
|
theme="popover"
|
||||||
:on-shown="() => tippyActions!.focus()"
|
:on-shown="() => tippyActions!.focus()"
|
||||||
>
|
>
|
||||||
<span class="select-wrapper">
|
<HoppButtonSecondary
|
||||||
<HoppButtonSecondary
|
class="!py-2 !px-0 capitalize"
|
||||||
class="ml-2 rounded-none pr-8 capitalize"
|
:label="embedOptions.theme"
|
||||||
:label="embedOptions.theme"
|
:icon="embedThemeIcon"
|
||||||
:icon="embedThemeIcon"
|
/>
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<template #content="{ hide }">
|
<template #content="{ hide }">
|
||||||
<div
|
<div
|
||||||
ref="tippyActions"
|
ref="tippyActions"
|
||||||
@@ -102,14 +101,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<span
|
||||||
|
class="flex justify-center mb-2 text-secondaryLight text-tiny"
|
||||||
|
>
|
||||||
|
{{ t("shared_requests.preview") }}
|
||||||
|
</span>
|
||||||
<ShareTemplatesEmbeds
|
<ShareTemplatesEmbeds
|
||||||
:endpoint="request?.endpoint"
|
:endpoint="request?.endpoint"
|
||||||
:method="request?.method"
|
:method="request?.method"
|
||||||
:model-value="embedOptions"
|
:model-value="embedOptions"
|
||||||
/>
|
/>
|
||||||
<div class="flex items-center justify-center py-4">
|
<div class="flex items-center justify-center">
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
:label="t('shared_requests.copy_html')"
|
:label="t('shared_requests.copy_html')"
|
||||||
|
class="underline text-secondaryDark"
|
||||||
@click="
|
@click="
|
||||||
copyContent({
|
copyContent({
|
||||||
widget: 'embed',
|
widget: 'embed',
|
||||||
@@ -126,12 +131,18 @@
|
|||||||
<div
|
<div
|
||||||
v-for="variant in buttonVariants"
|
v-for="variant in buttonVariants"
|
||||||
:key="variant.id"
|
: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" />
|
<ShareTemplatesButton :img="variant.img" />
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
:label="t('shared_requests.copy_html')"
|
:label="t('shared_requests.copy_html')"
|
||||||
|
class="underline text-secondaryDark"
|
||||||
@click="
|
@click="
|
||||||
copyContent({
|
copyContent({
|
||||||
widget: 'button',
|
widget: 'button',
|
||||||
@@ -142,6 +153,7 @@
|
|||||||
/>
|
/>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
:label="t('shared_requests.copy_markdown')"
|
:label="t('shared_requests.copy_markdown')"
|
||||||
|
class="underline text-secondaryDark"
|
||||||
@click="
|
@click="
|
||||||
copyContent({
|
copyContent({
|
||||||
widget: 'button',
|
widget: 'button',
|
||||||
@@ -157,12 +169,17 @@
|
|||||||
<div
|
<div
|
||||||
v-for="variant in linkVariants"
|
v-for="variant in linkVariants"
|
||||||
:key="variant.type"
|
: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" />
|
<ShareTemplatesLink :link="variant.link" :label="variant.label" />
|
||||||
|
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
:label="t(`shared_requests.copy_${variant.type}`)"
|
:label="t(`shared_requests.copy_${variant.type}`)"
|
||||||
|
class="underline text-secondaryDark"
|
||||||
@click="
|
@click="
|
||||||
copyContent({
|
copyContent({
|
||||||
widget: 'link',
|
widget: 'link',
|
||||||
@@ -205,6 +222,35 @@ const props = defineProps({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
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<{
|
const emit = defineEmits<{
|
||||||
@@ -220,6 +266,7 @@ const emit = defineEmits<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
const selectedWidget = useVModel(props, "modelValue")
|
const selectedWidget = useVModel(props, "modelValue")
|
||||||
|
const embedOptions = useVModel(props, "embedOptions")
|
||||||
|
|
||||||
type WidgetID = "embed" | "button" | "link"
|
type WidgetID = "embed" | "button" | "link"
|
||||||
|
|
||||||
@@ -254,42 +301,13 @@ type EmbedOption = {
|
|||||||
}[]
|
}[]
|
||||||
theme: "light" | "dark" | "system"
|
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(() => {
|
const embedThemeIcon = computed(() => {
|
||||||
if (embedOptions.value.theme === "system") {
|
if (embedOptions.value.theme === "system") {
|
||||||
return IconMonitor
|
return IconMonitor
|
||||||
} else if (embedOptions.value.theme === "light") {
|
} else if (embedOptions.value.theme === "light") {
|
||||||
return IconSun
|
return IconSun
|
||||||
} else {
|
|
||||||
return IconMoon
|
|
||||||
}
|
}
|
||||||
|
return IconMoon
|
||||||
})
|
})
|
||||||
|
|
||||||
const removeEmbedOption = (option: EmbedTabs) => {
|
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 copyEmbed = () => {
|
||||||
const options = embedOptions.value
|
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 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>`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const copyButton = (
|
const copyButton = (
|
||||||
@@ -377,20 +391,18 @@ const copyButton = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (type === "markdown") {
|
if (type === "markdown") {
|
||||||
return `[](${baseURL}/r/${props.request?.id})`
|
return `[](${shortcodeBaseURL}/r/${props.request?.id})`
|
||||||
} else {
|
|
||||||
return `<a href="${baseURL}/r/${props.request?.id}"><img src="${baseURL}/${badge}" alt="Run in Hoppscotch" /></a>`
|
|
||||||
}
|
}
|
||||||
|
return `<a href="${shortcodeBaseURL}/r/${props.request?.id}"><img src="${shortcodeBaseURL}/${badge}" alt="Run in Hoppscotch" /></a>`
|
||||||
}
|
}
|
||||||
|
|
||||||
const copyLink = (variationID: string) => {
|
const copyLink = (variationID: string) => {
|
||||||
if (variationID === "link1") {
|
if (variationID === "link1") {
|
||||||
return `${baseURL}/r/${props.request?.id}`
|
return `${shortcodeBaseURL}/r/${props.request?.id}`
|
||||||
} else if (variationID === "link2") {
|
} else if (variationID === "link2") {
|
||||||
return `<a href="${baseURL}/r/${props.request?.id}">Run in Hoppscotch</a>`
|
return `<a href="${shortcodeBaseURL}/r/${props.request?.id}">Run in Hoppscotch</a>`
|
||||||
} else {
|
|
||||||
return `[Run in Hoppscotch](${baseURL}/r/${props.request?.id})`
|
|
||||||
}
|
}
|
||||||
|
return `[Run in Hoppscotch](${shortcodeBaseURL}/r/${props.request?.id})`
|
||||||
}
|
}
|
||||||
|
|
||||||
const copyContent = ({
|
const copyContent = ({
|
||||||
@@ -410,7 +422,10 @@ const copyContent = ({
|
|||||||
} else {
|
} else {
|
||||||
content = copyEmbed()
|
content = copyEmbed()
|
||||||
}
|
}
|
||||||
const copyContent = { sharedRequestID: props.request?.id, content }
|
const copyContent = {
|
||||||
|
sharedRequestID: props.request?.id,
|
||||||
|
content,
|
||||||
|
}
|
||||||
emit("copy-shared-request", copyContent)
|
emit("copy-shared-request", copyContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
<HoppSmartModal
|
<HoppSmartModal
|
||||||
v-if="show"
|
v-if="show"
|
||||||
dialog
|
dialog
|
||||||
:title="t('modal.share_request')"
|
:title="
|
||||||
|
step === 1 ? t('modal.share_request') : t('modal.customize_request')
|
||||||
|
"
|
||||||
styles="sm:max-w-md"
|
styles="sm:max-w-md"
|
||||||
@close="hideModal"
|
@close="hideModal"
|
||||||
>
|
>
|
||||||
@@ -21,14 +23,14 @@
|
|||||||
<ShareCustomizeModal
|
<ShareCustomizeModal
|
||||||
v-else-if="step === 2"
|
v-else-if="step === 2"
|
||||||
v-model="selectedWidget"
|
v-model="selectedWidget"
|
||||||
|
v-model:embed-options="embedOptions"
|
||||||
:request="request"
|
:request="request"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
@copy-shared-request="copySharedRequest"
|
@copy-shared-request="copySharedRequest"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-if="step === 1" #footer>
|
||||||
<template #footer>
|
<div class="flex justify-start flex-1">
|
||||||
<div v-if="step === 1" class="flex justify-end">
|
|
||||||
<HoppButtonPrimary
|
<HoppButtonPrimary
|
||||||
:label="t('action.create')"
|
:label="t('action.create')"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
@@ -36,11 +38,12 @@
|
|||||||
/>
|
/>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
:label="t('action.cancel')"
|
:label="t('action.cancel')"
|
||||||
class="mr-2"
|
class="ml-2"
|
||||||
|
filled
|
||||||
|
outline
|
||||||
@click="hideModal"
|
@click="hideModal"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<HoppButtonPrimary v-else :label="t('action.close')" @click="hideModal" />
|
|
||||||
</template>
|
</template>
|
||||||
</HoppSmartModal>
|
</HoppSmartModal>
|
||||||
</template>
|
</template>
|
||||||
@@ -53,6 +56,18 @@ import { useI18n } from "~/composables/i18n"
|
|||||||
|
|
||||||
const t = useI18n()
|
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({
|
const props = defineProps({
|
||||||
request: {
|
request: {
|
||||||
type: Object as PropType<HoppRESTRequest | null>,
|
type: Object as PropType<HoppRESTRequest | null>,
|
||||||
@@ -75,6 +90,35 @@ const props = defineProps({
|
|||||||
type: Number,
|
type: Number,
|
||||||
default: 1,
|
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"
|
type WidgetID = "embed" | "button" | "link"
|
||||||
@@ -86,6 +130,7 @@ type Widget = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const selectedWidget = useVModel(props, "modelValue")
|
const selectedWidget = useVModel(props, "modelValue")
|
||||||
|
const embedOptions = useVModel(props, "embedOptions")
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "create-shared-request", request: HoppRESTRequest | null): void
|
(e: "create-shared-request", request: HoppRESTRequest | null): void
|
||||||
@@ -94,7 +139,7 @@ const emit = defineEmits<{
|
|||||||
(e: "update:step", value: number): void
|
(e: "update:step", value: number): void
|
||||||
(
|
(
|
||||||
e: "copy-shared-request",
|
e: "copy-shared-request",
|
||||||
request: {
|
payload: {
|
||||||
sharedRequestID: string | undefined
|
sharedRequestID: string | undefined
|
||||||
content: string | undefined
|
content: string | undefined
|
||||||
}
|
}
|
||||||
@@ -105,11 +150,11 @@ const createSharedRequest = () => {
|
|||||||
emit("create-shared-request", props.request as HoppRESTRequest)
|
emit("create-shared-request", props.request as HoppRESTRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
const copySharedRequest = (request: {
|
const copySharedRequest = (payload: {
|
||||||
sharedRequestID: string | undefined
|
sharedRequestID: string | undefined
|
||||||
content: string | undefined
|
content: string | undefined
|
||||||
}) => {
|
}) => {
|
||||||
emit("copy-shared-request", request)
|
emit("copy-shared-request", payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
const hideModal = () => {
|
const hideModal = () => {
|
||||||
|
|||||||
@@ -1,31 +1,31 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="group flex items-stretch"
|
class="flex items-stretch group"
|
||||||
@contextmenu.prevent="options!.tippy.show()"
|
@contextmenu.prevent="options!.tippy.show()"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-tippy="{ theme: 'tooltip', delay: [500, 20] }"
|
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}`"
|
:title="`${timeStamp}`"
|
||||||
@click="openInNewTab"
|
@click="openInNewTab"
|
||||||
>
|
>
|
||||||
<span
|
<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 }"
|
:style="{ color: requestLabelColor }"
|
||||||
>
|
>
|
||||||
<span class="truncate text-tiny font-semibold">
|
<span class="font-semibold truncate text-tiny">
|
||||||
{{ parseRequest.method }}
|
{{ parseRequest.method }}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</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">
|
<span class="flex-1 truncate">
|
||||||
{{ parseRequest.endpoint }}
|
{{ parseRequest.endpoint }}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</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 }}
|
{{ parseRequest.name }}
|
||||||
</span>
|
</span>
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
/>
|
/>
|
||||||
<HoppSmartItem
|
<HoppSmartItem
|
||||||
ref="customizeAction"
|
ref="customizeAction"
|
||||||
:icon="IconFileEdit"
|
:icon="IconCustomize"
|
||||||
:label="`${t('shared_requests.customize')}`"
|
:label="`${t('shared_requests.customize')}`"
|
||||||
:shortcut="['E']"
|
:shortcut="['E']"
|
||||||
@click="
|
@click="
|
||||||
@@ -110,7 +110,7 @@ import { getMethodLabelColorClassOf } from "~/helpers/rest/labelColoring"
|
|||||||
import { Shortcode } from "~/helpers/shortcode/Shortcode"
|
import { Shortcode } from "~/helpers/shortcode/Shortcode"
|
||||||
import IconArrowUpRight from "~icons/lucide/arrow-up-right-square"
|
import IconArrowUpRight from "~icons/lucide/arrow-up-right-square"
|
||||||
import IconMoreVertical from "~icons/lucide/more-vertical"
|
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 IconTrash2 from "~icons/lucide/trash-2"
|
||||||
import { shortDateTime } from "~/helpers/utils/date"
|
import { shortDateTime } from "~/helpers/utils/date"
|
||||||
|
|
||||||
@@ -121,7 +121,12 @@ const props = defineProps<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
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: "delete-shared-request", codeID: string): void
|
||||||
(e: "open-new-tab", request: HoppRESTRequest): 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 openInNewTabAction = ref<HTMLButtonElement | null>(null)
|
||||||
const customizeAction = ref<HTMLButtonElement | null>(null)
|
const customizeAction = ref<HTMLButtonElement | null>(null)
|
||||||
const deleteAction = ref<HTMLButtonElement | null>(null)
|
const deleteAction = ref<HTMLButtonElement | null>(null)
|
||||||
|
const options = ref<any | null>(null)
|
||||||
|
|
||||||
const parseRequest = computed(() =>
|
const parseRequest = computed(() =>
|
||||||
pipe(props.request.request, JSON.parse, translateToNewRequest)
|
pipe(props.request.request, JSON.parse, translateToNewRequest)
|
||||||
@@ -144,7 +150,13 @@ const openInNewTab = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const customizeSharedRequest = () => {
|
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 = () => {
|
const deleteSharedRequest = () => {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
to="https://docs.hoppscotch.io/documentation/features/shared-request"
|
to="https://docs.hoppscotch.io/documentation/features/share-requests"
|
||||||
blank
|
blank
|
||||||
:title="t('app.wiki')"
|
:title="t('app.wiki')"
|
||||||
:icon="IconHelpCircle"
|
:icon="IconHelpCircle"
|
||||||
@@ -32,10 +32,12 @@
|
|||||||
:alt="`${t('empty.shared_requests_logout')}`"
|
:alt="`${t('empty.shared_requests_logout')}`"
|
||||||
:text="`${t('empty.shared_requests_logout')}`"
|
:text="`${t('empty.shared_requests_logout')}`"
|
||||||
>
|
>
|
||||||
<HoppButtonPrimary
|
<template #body>
|
||||||
:label="t('auth.login')"
|
<HoppButtonPrimary
|
||||||
@click="invokeAction('modals.login.toggle')"
|
:label="t('auth.login')"
|
||||||
/>
|
@click="invokeAction('modals.login.toggle')"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
|
|
||||||
<template v-else-if="sharedRequests.length">
|
<template v-else-if="sharedRequests.length">
|
||||||
@@ -80,11 +82,12 @@
|
|||||||
/>
|
/>
|
||||||
<ShareModal
|
<ShareModal
|
||||||
v-model="selectedWidget"
|
v-model="selectedWidget"
|
||||||
|
v-model:embed-options="embedOptions"
|
||||||
|
:step="step"
|
||||||
:request="requestToShare"
|
:request="requestToShare"
|
||||||
:show="showShareRequestModal"
|
:show="showShareRequestModal"
|
||||||
:loading="shareRequestCreatingLoading"
|
:loading="shareRequestCreatingLoading"
|
||||||
:step="step"
|
@hide-modal="displayCustomizeRequestModal(false, null)"
|
||||||
@hide-modal="displayCustomizeRequestModal(false)"
|
|
||||||
@copy-shared-request="copySharedRequest"
|
@copy-shared-request="copySharedRequest"
|
||||||
@create-shared-request="createSharedRequest"
|
@create-shared-request="createSharedRequest"
|
||||||
/>
|
/>
|
||||||
@@ -105,6 +108,7 @@ import * as TE from "fp-ts/TaskEither"
|
|||||||
import {
|
import {
|
||||||
deleteShortcode as backendDeleteShortcode,
|
deleteShortcode as backendDeleteShortcode,
|
||||||
createShortcode,
|
createShortcode,
|
||||||
|
updateEmbedProperties,
|
||||||
} from "~/helpers/backend/mutations/Shortcode"
|
} from "~/helpers/backend/mutations/Shortcode"
|
||||||
import { GQLError } from "~/helpers/backend/GQLClient"
|
import { GQLError } from "~/helpers/backend/GQLClient"
|
||||||
import { useToast } from "~/composables/toast"
|
import { useToast } from "~/composables/toast"
|
||||||
@@ -114,6 +118,7 @@ import { copyToClipboard } from "~/helpers/utils/clipboard"
|
|||||||
import * as E from "fp-ts/Either"
|
import * as E from "fp-ts/Either"
|
||||||
import { RESTTabService } from "~/services/tab/rest"
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
|
import { watch } from "vue"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
@@ -130,6 +135,70 @@ const shareRequestCreatingLoading = ref(false)
|
|||||||
|
|
||||||
const requestToShare = ref<HoppRESTRequest | null>(null)
|
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 restTab = useService(RESTTabService)
|
||||||
|
|
||||||
const currentUser = useReadonlyStream(
|
const currentUser = useReadonlyStream(
|
||||||
@@ -139,6 +208,18 @@ const currentUser = useReadonlyStream(
|
|||||||
|
|
||||||
const step = ref(1)
|
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 WidgetID = "embed" | "button" | "link"
|
||||||
|
|
||||||
type Widget = {
|
type Widget = {
|
||||||
@@ -218,15 +299,73 @@ const displayShareRequestModal = (show: boolean) => {
|
|||||||
showShareRequestModal.value = show
|
showShareRequestModal.value = show
|
||||||
step.value = 1
|
step.value = 1
|
||||||
}
|
}
|
||||||
const displayCustomizeRequestModal = (show: boolean) => {
|
|
||||||
|
const displayCustomizeRequestModal = (
|
||||||
|
show: boolean,
|
||||||
|
embedProperties?: string | null
|
||||||
|
) => {
|
||||||
showShareRequestModal.value = show
|
showShareRequestModal.value = show
|
||||||
step.value = 2
|
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) => {
|
const createSharedRequest = async (request: HoppRESTRequest | null) => {
|
||||||
if (request && selectedWidget.value) {
|
if (request && selectedWidget.value) {
|
||||||
|
const properties = {
|
||||||
|
options: ["parameters", "body", "headers"],
|
||||||
|
theme: "system",
|
||||||
|
}
|
||||||
shareRequestCreatingLoading.value = true
|
shareRequestCreatingLoading.value = true
|
||||||
const sharedRequestResult = await createShortcode(request)()
|
const sharedRequestResult = await createShortcode(
|
||||||
|
request,
|
||||||
|
selectedWidget.value.value === "embed"
|
||||||
|
? JSON.stringify(properties)
|
||||||
|
: undefined
|
||||||
|
)()
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
platform.analytics?.logEvent({
|
||||||
type: "HOPP_SHORTCODE_CREATED",
|
type: "HOPP_SHORTCODE_CREATED",
|
||||||
@@ -243,6 +382,23 @@ const createSharedRequest = async (request: HoppRESTRequest | null) => {
|
|||||||
id: sharedRequestResult.right.createShortcode.id,
|
id: sharedRequestResult.right.createShortcode.id,
|
||||||
}
|
}
|
||||||
step.value = 2
|
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 = (
|
const customizeSharedRequest = (
|
||||||
request: HoppRESTRequest,
|
request: HoppRESTRequest,
|
||||||
shredRequestID: string
|
shredRequestID: string,
|
||||||
|
embedProperties?: string | null
|
||||||
) => {
|
) => {
|
||||||
requestToShare.value = {
|
requestToShare.value = {
|
||||||
...request,
|
...request,
|
||||||
id: shredRequestID,
|
id: shredRequestID,
|
||||||
}
|
}
|
||||||
displayCustomizeRequestModal(true)
|
displayCustomizeRequestModal(true, embedProperties)
|
||||||
}
|
}
|
||||||
|
|
||||||
const copySharedRequest = (request: {
|
const copySharedRequest = (payload: {
|
||||||
sharedRequestID: string | undefined
|
sharedRequestID: string | undefined
|
||||||
content: string | undefined
|
content: string | undefined
|
||||||
}) => {
|
}) => {
|
||||||
if (request.content) {
|
if (payload.content) {
|
||||||
copyToClipboard(request.content)
|
copyToClipboard(payload.content)
|
||||||
toast.success(t("state.copied_to_clipboard"))
|
toast.success(t("state.copied_to_clipboard"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -291,13 +448,12 @@ const resolveConfirmModal = (title: string | null) => {
|
|||||||
const getErrorMessage = (err: GQLError<string>) => {
|
const getErrorMessage = (err: GQLError<string>) => {
|
||||||
if (err.type === "network_error") {
|
if (err.type === "network_error") {
|
||||||
return t("error.network_error")
|
return t("error.network_error")
|
||||||
} else {
|
}
|
||||||
switch (err.error) {
|
switch (err.error) {
|
||||||
case "shortcode/not_found":
|
case "shortcode/not_found":
|
||||||
return t("shared_request.not_found")
|
return t("shared_request.not_found")
|
||||||
default:
|
default:
|
||||||
return t("error.something_went_wrong")
|
return t("error.something_went_wrong")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div class="flex flex-col items-center p-4 border rounded border-dividerDark">
|
||||||
class="flex items-center justify-center rounded border border-dotted border-dividerDark p-5"
|
<img :src="img" :alt="t('shared_requests.run_in_hoppscotch')" />
|
||||||
>
|
|
||||||
<a href="/" target="_blank">
|
|
||||||
<img :src="img" :alt="t('shared_requests.run_in_hoppscotch')" />
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +1,32 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="flex flex-col rounded border border-dotted border-divider p-5"
|
class="flex flex-col p-4 border rounded border-dividerDark"
|
||||||
:class="{
|
:class="{
|
||||||
'bg-accentContrast': isEmbedThemeLight,
|
'bg-accentContrast': isEmbedThemeLight,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="flex items-stretch space-x-4 rounded border-divider"
|
class="flex items-stretch space-x-2"
|
||||||
:class="{
|
:class="{
|
||||||
'bg-accentContrast': isEmbedThemeLight,
|
'bg-accentContrast': isEmbedThemeLight,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="flex max-w-[4rem] items-center justify-center rounded border border-divider px-1 py-2 text-tiny"
|
class="flex items-center flex-1 min-w-0 border rounded border-divider"
|
||||||
: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"
|
|
||||||
>
|
>
|
||||||
<span
|
<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="{
|
:class="{
|
||||||
'text-primary': isEmbedThemeLight,
|
'text-primary': isEmbedThemeLight,
|
||||||
}"
|
}"
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<button
|
<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="{
|
:class="{
|
||||||
'!bg-accentContrast text-primaryLight': isEmbedThemeLight,
|
'!bg-accentContrast text-primaryLight': isEmbedThemeLight,
|
||||||
}"
|
}"
|
||||||
@@ -44,10 +44,10 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="flex border-divider"
|
class="flex"
|
||||||
:class="{
|
:class="{
|
||||||
'bg-accentContrast text-primary': isEmbedThemeLight,
|
'bg-accentContrast text-primary': isEmbedThemeLight,
|
||||||
'border-b pt-2 ': !noActiveTab,
|
'border-b border-divider pt-2': !noActiveTab,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@@ -57,7 +57,8 @@
|
|||||||
class="px-2 py-2"
|
class="px-2 py-2"
|
||||||
:class="{
|
:class="{
|
||||||
'border-b border-dividerDark':
|
'border-b border-dividerDark':
|
||||||
embedOptions.selectedTab === option.value,
|
embedOptions.tabs.filter((tab) => tab.enabled)[0]?.value ===
|
||||||
|
option.value,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
{{ option.label }}
|
{{ option.label }}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div class="flex flex-col items-center p-4 border rounded border-dividerDark">
|
||||||
class="flex items-center justify-center rounded border border-dotted border-dividerDark p-5"
|
|
||||||
>
|
|
||||||
<span
|
<span
|
||||||
:class="{
|
:class="{
|
||||||
'border-b border-secondary': label,
|
'border-b border-secondary': label,
|
||||||
@@ -23,7 +21,12 @@ const props = defineProps<{
|
|||||||
label?: string | undefined
|
label?: string | undefined
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const shortcodeBaseURL =
|
||||||
|
import.meta.env.VITE_SHORTCODE_BASE_URL ?? "https://hopp.sh"
|
||||||
|
|
||||||
const text = computed(() => {
|
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>
|
</script>
|
||||||
|
|||||||
@@ -60,7 +60,7 @@
|
|||||||
:text="`${t('state.nothing_found')} ‟${searchQuery}”`"
|
:text="`${t('state.nothing_found')} ‟${searchQuery}”`"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<icon-lucide-search class="svg-icons pb-2 opacity-75" />
|
<icon-lucide-search class="svg-icons opacity-75" />
|
||||||
</template>
|
</template>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -45,15 +45,17 @@
|
|||||||
:alt="`${t('empty.members')}`"
|
:alt="`${t('empty.members')}`"
|
||||||
:text="t('empty.members')"
|
:text="t('empty.members')"
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<template #body>
|
||||||
:icon="IconUserPlus"
|
<HoppButtonSecondary
|
||||||
:label="t('team.invite')"
|
:icon="IconUserPlus"
|
||||||
@click="
|
:label="t('team.invite')"
|
||||||
() => {
|
@click="
|
||||||
emit('invite-team')
|
() => {
|
||||||
}
|
emit('invite-team')
|
||||||
"
|
}
|
||||||
/>
|
"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
<div v-else class="divide-y divide-dividerLight">
|
<div v-else class="divide-y divide-dividerLight">
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -125,8 +125,7 @@
|
|||||||
pendingInvites.data.right.team?.teamInvitations.length === 0
|
pendingInvites.data.right.team?.teamInvitations.length === 0
|
||||||
"
|
"
|
||||||
:text="t('empty.pending_invites')"
|
:text="t('empty.pending_invites')"
|
||||||
>
|
/>
|
||||||
</HoppSmartPlaceholder>
|
|
||||||
<div
|
<div
|
||||||
v-if="!pendingInvites.loading && E.isLeft(pendingInvites.data)"
|
v-if="!pendingInvites.loading && E.isLeft(pendingInvites.data)"
|
||||||
class="flex flex-col items-center p-4"
|
class="flex flex-col items-center p-4"
|
||||||
@@ -245,11 +244,13 @@
|
|||||||
:alt="`${t('empty.invites')}`"
|
:alt="`${t('empty.invites')}`"
|
||||||
:text="`${t('empty.invites')}`"
|
:text="`${t('empty.invites')}`"
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<template #body>
|
||||||
:label="t('add.new')"
|
<HoppButtonSecondary
|
||||||
filled
|
:label="t('add.new')"
|
||||||
@click="addNewInvitee"
|
filled
|
||||||
/>
|
@click="addNewInvitee"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -16,11 +16,13 @@
|
|||||||
:alt="`${t('empty.teams')}`"
|
:alt="`${t('empty.teams')}`"
|
||||||
:text="`${t('empty.teams')}`"
|
:text="`${t('empty.teams')}`"
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<template #body>
|
||||||
:label="`${t('team.create_new')}`"
|
<HoppButtonSecondary
|
||||||
filled
|
:label="`${t('team.create_new')}`"
|
||||||
@click="displayModalAdd(true)"
|
filled
|
||||||
/>
|
@click="displayModalAdd(true)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
<div
|
<div
|
||||||
v-else-if="!loading"
|
v-else-if="!loading"
|
||||||
|
|||||||
@@ -21,13 +21,15 @@
|
|||||||
:alt="`${t('empty.teams')}`"
|
:alt="`${t('empty.teams')}`"
|
||||||
:text="`${t('empty.teams')}`"
|
:text="`${t('empty.teams')}`"
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<template #body>
|
||||||
:label="t('team.create_new')"
|
<HoppButtonSecondary
|
||||||
filled
|
:label="t('team.create_new')"
|
||||||
outline
|
filled
|
||||||
:icon="IconPlus"
|
outline
|
||||||
@click="displayModalAdd(true)"
|
:icon="IconPlus"
|
||||||
/>
|
@click="displayModalAdd(true)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
<div v-else-if="!loading" class="flex flex-col">
|
<div v-else-if="!loading" class="flex flex-col">
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { flow } from "fp-ts/function"
|
|
||||||
import { useI18n as _useI18n } from "vue-i18n"
|
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
|
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 { Observable, Subject } from "rxjs"
|
||||||
import { filter } from "rxjs/operators"
|
import { filter } from "rxjs/operators"
|
||||||
import { flow, pipe } from "fp-ts/function"
|
import { Ref } from "vue"
|
||||||
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 {
|
import {
|
||||||
environmentsStore,
|
environmentsStore,
|
||||||
getCurrentEnvironment,
|
getCurrentEnvironment,
|
||||||
@@ -29,9 +18,18 @@ import {
|
|||||||
setGlobalEnvVariables,
|
setGlobalEnvVariables,
|
||||||
updateEnvironment,
|
updateEnvironment,
|
||||||
} from "~/newstore/environments"
|
} from "~/newstore/environments"
|
||||||
import { Ref } from "vue"
|
|
||||||
import { HoppTab } from "~/services/tab"
|
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 { 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 = (
|
const getTestableBody = (
|
||||||
res: HoppRESTResponse & { type: "success" | "fail" }
|
res: HoppRESTResponse & { type: "success" | "fail" }
|
||||||
@@ -89,7 +87,7 @@ export function runRESTRequest$(
|
|||||||
const res = getFinalEnvsFromPreRequest(
|
const res = getFinalEnvsFromPreRequest(
|
||||||
tab.value.document.request.preRequestScript,
|
tab.value.document.request.preRequestScript,
|
||||||
getCombinedEnvVariables()
|
getCombinedEnvVariables()
|
||||||
)().then((envs) => {
|
).then((envs) => {
|
||||||
if (cancelCalled) return E.left("cancellation" as const)
|
if (cancelCalled) return E.left("cancellation" as const)
|
||||||
|
|
||||||
if (E.isLeft(envs)) {
|
if (E.isLeft(envs)) {
|
||||||
@@ -125,7 +123,7 @@ export function runRESTRequest$(
|
|||||||
body: getTestableBody(res),
|
body: getTestableBody(res),
|
||||||
headers: res.headers,
|
headers: res.headers,
|
||||||
}
|
}
|
||||||
)()
|
)
|
||||||
|
|
||||||
if (E.isRight(runResult)) {
|
if (E.isRight(runResult)) {
|
||||||
tab.value.document.testResults = translateToSandboxTestResults(
|
tab.value.document.testResults = translateToSandboxTestResults(
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
mutation CreateShortcode($request: String!) {
|
mutation CreateShortcode($request: String!, $properties: String) {
|
||||||
createShortcode(request: $request) {
|
createShortcode(request: $request, properties: $properties) {
|
||||||
id
|
id
|
||||||
request
|
request
|
||||||
createdOn
|
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) {
|
shortcode(code: $code) {
|
||||||
id
|
id
|
||||||
request
|
request
|
||||||
|
properties
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
subscription ShortcodeUpdated {
|
||||||
|
myShortcodesUpdated {
|
||||||
|
id
|
||||||
|
request
|
||||||
|
createdOn
|
||||||
|
properties
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,15 +7,22 @@ import {
|
|||||||
DeleteShortcodeDocument,
|
DeleteShortcodeDocument,
|
||||||
DeleteShortcodeMutation,
|
DeleteShortcodeMutation,
|
||||||
DeleteShortcodeMutationVariables,
|
DeleteShortcodeMutationVariables,
|
||||||
|
UpdateEmbedPropertiesDocument,
|
||||||
|
UpdateEmbedPropertiesMutation,
|
||||||
|
UpdateEmbedPropertiesMutationVariables,
|
||||||
} from "../graphql"
|
} from "../graphql"
|
||||||
|
|
||||||
type DeleteShortcodeErrors = "shortcode/not_found"
|
type DeleteShortcodeErrors = "shortcode/not_found"
|
||||||
|
|
||||||
export const createShortcode = (request: HoppRESTRequest) =>
|
export const createShortcode = (
|
||||||
|
request: HoppRESTRequest,
|
||||||
|
properties?: string
|
||||||
|
) =>
|
||||||
runMutation<CreateShortcodeMutation, CreateShortcodeMutationVariables, "">(
|
runMutation<CreateShortcodeMutation, CreateShortcodeMutationVariables, "">(
|
||||||
CreateShortcodeDocument,
|
CreateShortcodeDocument,
|
||||||
{
|
{
|
||||||
request: JSON.stringify(request),
|
request: JSON.stringify(request),
|
||||||
|
properties,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,3 +34,13 @@ export const deleteShortcode = (code: string) =>
|
|||||||
>(DeleteShortcodeDocument, {
|
>(DeleteShortcodeDocument, {
|
||||||
code,
|
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 { Environment } from "@hoppscotch/data"
|
||||||
import { cloneDeep } from "lodash-es"
|
import { cloneDeep } from "lodash-es"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getCurrentEnvironment,
|
getCurrentEnvironment,
|
||||||
getGlobalVariables,
|
getGlobalVariables,
|
||||||
} from "~/newstore/environments"
|
} from "~/newstore/environments"
|
||||||
|
import { TestResult } from "@hoppscotch/js-sandbox"
|
||||||
|
|
||||||
export const getCombinedEnvVariables = () => ({
|
export const getCombinedEnvVariables = () => ({
|
||||||
global: cloneDeep(getGlobalVariables()),
|
global: cloneDeep(getGlobalVariables()),
|
||||||
@@ -17,4 +20,5 @@ export const getFinalEnvsFromPreRequest = (
|
|||||||
global: Environment["variables"]
|
global: Environment["variables"]
|
||||||
selected: Environment["variables"]
|
selected: Environment["variables"]
|
||||||
}
|
}
|
||||||
) => runPreRequestScript(script, envs)
|
): Promise<E.Either<string, TestResult["envs"]>> =>
|
||||||
|
runPreRequestScript(script, envs)
|
||||||
|
|||||||
@@ -4,6 +4,6 @@
|
|||||||
export interface Shortcode {
|
export interface Shortcode {
|
||||||
id: string
|
id: string
|
||||||
request: string
|
request: string
|
||||||
properties?: string | null | undefined
|
properties?: string | null
|
||||||
createdOn: Date
|
createdOn: Date
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
GetUserShortcodesDocument,
|
GetUserShortcodesDocument,
|
||||||
ShortcodeCreatedDocument,
|
ShortcodeCreatedDocument,
|
||||||
ShortcodeDeletedDocument,
|
ShortcodeDeletedDocument,
|
||||||
|
ShortcodeUpdatedDocument,
|
||||||
} from "../backend/graphql"
|
} from "../backend/graphql"
|
||||||
import { BACKEND_PAGE_SIZE } from "../backend/helpers"
|
import { BACKEND_PAGE_SIZE } from "../backend/helpers"
|
||||||
import { Shortcode } from "./Shortcode"
|
import { Shortcode } from "./Shortcode"
|
||||||
@@ -25,9 +26,11 @@ export default class ShortcodeListAdapter {
|
|||||||
|
|
||||||
private shortcodeCreated: Subscription | null
|
private shortcodeCreated: Subscription | null
|
||||||
private shortcodeRevoked: Subscription | null
|
private shortcodeRevoked: Subscription | null
|
||||||
|
private shortcodeUpdated: Subscription | null
|
||||||
|
|
||||||
private shortcodeCreatedSub: WSubscription | null
|
private shortcodeCreatedSub: WSubscription | null
|
||||||
private shortcodeRevokedSub: WSubscription | null
|
private shortcodeRevokedSub: WSubscription | null
|
||||||
|
private shortcodeUpdatedSub: WSubscription | null
|
||||||
|
|
||||||
constructor(deferInit = false) {
|
constructor(deferInit = false) {
|
||||||
this.error$ = new BehaviorSubject<GQLError<string> | null>(null)
|
this.error$ = new BehaviorSubject<GQLError<string> | null>(null)
|
||||||
@@ -39,8 +42,10 @@ export default class ShortcodeListAdapter {
|
|||||||
this.isDispose = true
|
this.isDispose = true
|
||||||
this.shortcodeCreated = null
|
this.shortcodeCreated = null
|
||||||
this.shortcodeRevoked = null
|
this.shortcodeRevoked = null
|
||||||
|
this.shortcodeUpdated = null
|
||||||
this.shortcodeCreatedSub = null
|
this.shortcodeCreatedSub = null
|
||||||
this.shortcodeRevokedSub = null
|
this.shortcodeRevokedSub = null
|
||||||
|
this.shortcodeUpdatedSub = null
|
||||||
|
|
||||||
if (!deferInit) this.initialize()
|
if (!deferInit) this.initialize()
|
||||||
}
|
}
|
||||||
@@ -48,8 +53,10 @@ export default class ShortcodeListAdapter {
|
|||||||
unsubscribeSubscriptions() {
|
unsubscribeSubscriptions() {
|
||||||
this.shortcodeCreated?.unsubscribe()
|
this.shortcodeCreated?.unsubscribe()
|
||||||
this.shortcodeRevoked?.unsubscribe()
|
this.shortcodeRevoked?.unsubscribe()
|
||||||
|
this.shortcodeUpdated?.unsubscribe()
|
||||||
this.shortcodeCreatedSub?.unsubscribe()
|
this.shortcodeCreatedSub?.unsubscribe()
|
||||||
this.shortcodeRevokedSub?.unsubscribe()
|
this.shortcodeRevokedSub?.unsubscribe()
|
||||||
|
this.shortcodeUpdatedSub?.unsubscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
@@ -137,6 +144,14 @@ export default class ShortcodeListAdapter {
|
|||||||
this.shortcodes$.next(newShortcode)
|
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() {
|
private registerSubscriptions() {
|
||||||
const [shortcodeCreated$, shortcodeCreatedSub] = runAuthOnlyGQLSubscription(
|
const [shortcodeCreated$, shortcodeCreatedSub] = runAuthOnlyGQLSubscription(
|
||||||
{
|
{
|
||||||
@@ -169,5 +184,21 @@ export default class ShortcodeListAdapter {
|
|||||||
|
|
||||||
this.deleteSharedRequest(result.right.myShortcodesRevoked.id)
|
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>
|
<template>
|
||||||
<div class="flex flex-col items-center justify-between p-8">
|
<div class="flex flex-col flex-1 w-full">
|
||||||
Temporary page for Embed till the feature is ready
|
<Embeds
|
||||||
|
v-if="tab"
|
||||||
|
v-model:modelTab="tab"
|
||||||
|
:properties="properties"
|
||||||
|
:shared-request-i-d="sharedRequestID"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</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')}`"
|
:alt="`${t('empty.profile')}`"
|
||||||
:text="`${t('empty.profile')}`"
|
:text="`${t('empty.profile')}`"
|
||||||
>
|
>
|
||||||
<HoppButtonPrimary
|
<template #body>
|
||||||
:label="t('auth.login')"
|
<HoppButtonPrimary
|
||||||
@click="invokeAction('modals.login.toggle')"
|
:label="t('auth.login')"
|
||||||
/>
|
@click="invokeAction('modals.login.toggle')"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
<div v-else class="space-y-8">
|
<div v-else class="space-y-8">
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -5,10 +5,11 @@
|
|||||||
content-styles="!h-[calc(100%-var(--sidebar-primary-sticky-fold)-1px)] !flex"
|
content-styles="!h-[calc(100%-var(--sidebar-primary-sticky-fold)-1px)] !flex"
|
||||||
>
|
>
|
||||||
<HoppSmartTab
|
<HoppSmartTab
|
||||||
v-for="{ target, title } in REALTIME_NAVIGATION"
|
v-for="(navigation, index) in REALTIME_NAVIGATION"
|
||||||
:id="target"
|
:id="navigation.target"
|
||||||
:key="target"
|
:key="index"
|
||||||
:label="title"
|
:label="navigation.title"
|
||||||
|
:icon="navigation.icon"
|
||||||
>
|
>
|
||||||
<RouterView />
|
<RouterView />
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
@@ -20,6 +21,10 @@ import { watch, ref, computed } from "vue"
|
|||||||
import { RouterView, useRouter, useRoute } from "vue-router"
|
import { RouterView, useRouter, useRoute } from "vue-router"
|
||||||
import { usePageHead } from "~/composables/head"
|
import { usePageHead } from "~/composables/head"
|
||||||
import { useI18n } from "~/composables/i18n"
|
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 t = useI18n()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -29,18 +34,22 @@ const REALTIME_NAVIGATION = [
|
|||||||
{
|
{
|
||||||
target: "websocket",
|
target: "websocket",
|
||||||
title: t("tab.websocket"),
|
title: t("tab.websocket"),
|
||||||
|
icon: IconWebsocket,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
target: "sse",
|
target: "sse",
|
||||||
title: t("tab.sse"),
|
title: t("tab.sse"),
|
||||||
|
icon: IconSse,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
target: "socketio",
|
target: "socketio",
|
||||||
title: t("tab.socketio"),
|
title: t("tab.socketio"),
|
||||||
|
icon: IconSocketio,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
target: "mqtt",
|
target: "mqtt",
|
||||||
title: t("tab.mqtt"),
|
title: t("tab.mqtt"),
|
||||||
|
icon: IconMqtt,
|
||||||
},
|
},
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
|
|||||||
@@ -142,12 +142,14 @@
|
|||||||
:alt="`${t('empty.subscription')}`"
|
:alt="`${t('empty.subscription')}`"
|
||||||
:text="`${t('empty.subscription')}`"
|
:text="`${t('empty.subscription')}`"
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<template #body>
|
||||||
:label="t('mqtt.new')"
|
<HoppButtonSecondary
|
||||||
filled
|
:label="t('mqtt.new')"
|
||||||
outline
|
filled
|
||||||
@click="showSubscriptionModal(true)"
|
outline
|
||||||
/>
|
@click="showSubscriptionModal(true)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -197,14 +197,16 @@
|
|||||||
:alt="`${t('socketio.connection_not_authorized')}`"
|
:alt="`${t('socketio.connection_not_authorized')}`"
|
||||||
:text="`${t('socketio.connection_not_authorized')}`"
|
:text="`${t('socketio.connection_not_authorized')}`"
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<template #body>
|
||||||
outline
|
<HoppButtonSecondary
|
||||||
:label="t('app.documentation')"
|
outline
|
||||||
to="https://docs.hoppscotch.io/documentation/features/authorization"
|
:label="t('app.documentation')"
|
||||||
blank
|
to="https://docs.hoppscotch.io/documentation/features/authorization"
|
||||||
:icon="IconExternalLink"
|
blank
|
||||||
reverse
|
:icon="IconExternalLink"
|
||||||
/>
|
reverse
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
<div
|
<div
|
||||||
v-if="authType === 'Bearer'"
|
v-if="authType === 'Bearer'"
|
||||||
|
|||||||
@@ -163,8 +163,7 @@
|
|||||||
:src="`/images/states/${colorMode.value}/add_category.svg`"
|
:src="`/images/states/${colorMode.value}/add_category.svg`"
|
||||||
:alt="`${t('empty.protocols')}`"
|
:alt="`${t('empty.protocols')}`"
|
||||||
:text="`${t('empty.protocols')}`"
|
:text="`${t('empty.protocols')}`"
|
||||||
>
|
/>
|
||||||
</HoppSmartPlaceholder>
|
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
</HoppSmartTabs>
|
</HoppSmartTabs>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { HoppModule } from "~/modules"
|
|||||||
import { InspectorsPlatformDef } from "./inspectors"
|
import { InspectorsPlatformDef } from "./inspectors"
|
||||||
import { Service } from "dioc"
|
import { Service } from "dioc"
|
||||||
import { IOPlatformDef } from "./io"
|
import { IOPlatformDef } from "./io"
|
||||||
|
import { SpotlightPlatformDef } from "./spotlight"
|
||||||
|
|
||||||
export type PlatformDef = {
|
export type PlatformDef = {
|
||||||
ui?: UIPlatformDef
|
ui?: UIPlatformDef
|
||||||
@@ -28,6 +29,7 @@ export type PlatformDef = {
|
|||||||
}
|
}
|
||||||
interceptors: InterceptorsPlatformDef
|
interceptors: InterceptorsPlatformDef
|
||||||
additionalInspectors?: InspectorsPlatformDef
|
additionalInspectors?: InspectorsPlatformDef
|
||||||
|
spotlight?: SpotlightPlatformDef
|
||||||
platformFeatureFlags: {
|
platformFeatureFlags: {
|
||||||
exportAsGIST: boolean
|
exportAsGIST: boolean
|
||||||
hasTelemetry: 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[]) => {
|
const getBannerWithHighestScore = (list: Banner[]) => {
|
||||||
if (list.length === 0) return null
|
if (list.length === 0) return null
|
||||||
else if (list.length === 1) return list[0]
|
else if (list.length === 1) return list[0]
|
||||||
else {
|
|
||||||
const highestScore = Math.max(...list.map((banner) => banner.content.score))
|
const highestScore = Math.max(...list.map((banner) => banner.content.score))
|
||||||
return list.find((banner) => banner.content.score === highestScore)
|
return list.find((banner) => banner.content.score === highestScore)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ export type SpotlightSearcherResult = {
|
|||||||
* The keyboard shortcut to trigger the result
|
* The keyboard shortcut to trigger the result
|
||||||
*/
|
*/
|
||||||
keyboardShortcut?: string[]
|
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",
|
preset: "ts-jest",
|
||||||
testEnvironment: "jsdom",
|
testEnvironment: "jsdom",
|
||||||
collectCoverage: true,
|
collectCoverage: true,
|
||||||
setupFilesAfterEnv: ["./jest.setup.ts"],
|
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",
|
"name": "@hoppscotch/js-sandbox",
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"description": "JavaScript sandboxes for running external scripts used by Hoppscotch clients",
|
"description": "JavaScript sandboxes for running external scripts used by Hoppscotch clients",
|
||||||
"main": "./lib/index.js",
|
"type": "module",
|
||||||
"module": "./lib/index.mjs",
|
"files": [
|
||||||
"type": "commonjs",
|
"dist",
|
||||||
|
"index.d.ts"
|
||||||
|
],
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
"require": "./lib/index.js",
|
"types": "./dist/types/index.d.ts"
|
||||||
"default": "./lib/index.mjs"
|
},
|
||||||
|
"./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": {
|
"engines": {
|
||||||
"node": ">=14",
|
"node": ">=14",
|
||||||
"pnpm": ">=3"
|
"pnpm": ">=3"
|
||||||
@@ -20,7 +31,7 @@
|
|||||||
"lint": "eslint --ext .ts,.js --ignore-path .gitignore .",
|
"lint": "eslint --ext .ts,.js --ignore-path .gitignore .",
|
||||||
"lintfix": "eslint --fix --ext .ts,.js --ignore-path .gitignore .",
|
"lintfix": "eslint --fix --ext .ts,.js --ignore-path .gitignore .",
|
||||||
"test": "pnpm exec jest",
|
"test": "pnpm exec jest",
|
||||||
"build": "pnpm exec tsup",
|
"build": "vite build && tsc --emitDeclarationOnly",
|
||||||
"clean": "pnpm tsc --build --clean",
|
"clean": "pnpm tsc --build --clean",
|
||||||
"postinstall": "pnpm run build",
|
"postinstall": "pnpm run build",
|
||||||
"prepublish": "pnpm run build",
|
"prepublish": "pnpm run build",
|
||||||
@@ -41,10 +52,10 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hoppscotch/data": "workspace:^",
|
"@hoppscotch/data": "workspace:^",
|
||||||
|
"@types/lodash-es": "^4.17.12",
|
||||||
"fp-ts": "^2.11.10",
|
"fp-ts": "^2.11.10",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"quickjs-emscripten": "^0.15.0",
|
"lodash-es": "^4.17.21"
|
||||||
"tsup": "^5.12.5"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@digitak/esrun": "^3.1.2",
|
"@digitak/esrun": "^3.1.2",
|
||||||
@@ -61,6 +72,7 @@
|
|||||||
"jest": "^27.5.1",
|
"jest": "^27.5.1",
|
||||||
"prettier": "^2.8.4",
|
"prettier": "^2.8.4",
|
||||||
"ts-jest": "^27.1.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"
|
import "@relmify/jest-fp-ts"
|
||||||
|
|
||||||
describe("execPreRequestScript", () => {
|
describe("execPreRequestScript", () => {
|
||||||
test("returns the updated envirionment properly", () => {
|
test("returns the updated envirionment properly", () => {
|
||||||
return expect(
|
return expect(
|
||||||
execPreRequestScript(
|
runPreRequestScript(
|
||||||
`
|
`
|
||||||
pw.env.set("bob", "newbob")
|
pw.env.set("bob", "newbob")
|
||||||
`,
|
`,
|
||||||
@@ -27,7 +27,7 @@ describe("execPreRequestScript", () => {
|
|||||||
|
|
||||||
test("fails if the key is not a string", () => {
|
test("fails if the key is not a string", () => {
|
||||||
return expect(
|
return expect(
|
||||||
execPreRequestScript(
|
runPreRequestScript(
|
||||||
`
|
`
|
||||||
pw.env.set(10, "newbob")
|
pw.env.set(10, "newbob")
|
||||||
`,
|
`,
|
||||||
@@ -44,7 +44,7 @@ describe("execPreRequestScript", () => {
|
|||||||
|
|
||||||
test("fails if the value is not a string", () => {
|
test("fails if the value is not a string", () => {
|
||||||
return expect(
|
return expect(
|
||||||
execPreRequestScript(
|
runPreRequestScript(
|
||||||
`
|
`
|
||||||
pw.env.set("bob", 10)
|
pw.env.set("bob", 10)
|
||||||
`,
|
`,
|
||||||
@@ -61,7 +61,7 @@ describe("execPreRequestScript", () => {
|
|||||||
|
|
||||||
test("fails for invalid syntax", () => {
|
test("fails for invalid syntax", () => {
|
||||||
return expect(
|
return expect(
|
||||||
execPreRequestScript(
|
runPreRequestScript(
|
||||||
`
|
`
|
||||||
pw.env.set("bob",
|
pw.env.set("bob",
|
||||||
`,
|
`,
|
||||||
@@ -78,7 +78,7 @@ describe("execPreRequestScript", () => {
|
|||||||
|
|
||||||
test("creates new env variable if doesn't exist", () => {
|
test("creates new env variable if doesn't exist", () => {
|
||||||
return expect(
|
return expect(
|
||||||
execPreRequestScript(
|
runPreRequestScript(
|
||||||
`
|
`
|
||||||
pw.env.set("foo", "bar")
|
pw.env.set("foo", "bar")
|
||||||
`,
|
`,
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
import "@relmify/jest-fp-ts"
|
||||||
import * as TE from "fp-ts/TaskEither"
|
import * as TE from "fp-ts/TaskEither"
|
||||||
import { pipe } from "fp-ts/function"
|
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 = {
|
const fakeResponse: TestResponse = {
|
||||||
status: 200,
|
status: 200,
|
||||||
@@ -12,7 +13,7 @@ const fakeResponse: TestResponse = {
|
|||||||
|
|
||||||
const func = (script: string, envs: TestResult["envs"]) =>
|
const func = (script: string, envs: TestResult["envs"]) =>
|
||||||
pipe(
|
pipe(
|
||||||
execTestScript(script, envs, fakeResponse),
|
runTestScript(script, envs, fakeResponse),
|
||||||
TE.map((x) => x.tests)
|
TE.map((x) => x.tests)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
import "@relmify/jest-fp-ts"
|
||||||
import * as TE from "fp-ts/TaskEither"
|
import * as TE from "fp-ts/TaskEither"
|
||||||
import { pipe } from "fp-ts/function"
|
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 = {
|
const fakeResponse: TestResponse = {
|
||||||
status: 200,
|
status: 200,
|
||||||
@@ -12,7 +13,7 @@ const fakeResponse: TestResponse = {
|
|||||||
|
|
||||||
const func = (script: string, envs: TestResult["envs"]) =>
|
const func = (script: string, envs: TestResult["envs"]) =>
|
||||||
pipe(
|
pipe(
|
||||||
execTestScript(script, envs, fakeResponse),
|
runTestScript(script, envs, fakeResponse),
|
||||||
TE.map((x) => x.tests)
|
TE.map((x) => x.tests)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { pipe } from "fp-ts/function"
|
|
||||||
import * as TE from "fp-ts/TaskEither"
|
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 = {
|
const fakeResponse: TestResponse = {
|
||||||
status: 200,
|
status: 200,
|
||||||
@@ -10,7 +12,7 @@ const fakeResponse: TestResponse = {
|
|||||||
|
|
||||||
const func = (script: string, envs: TestResult["envs"]) =>
|
const func = (script: string, envs: TestResult["envs"]) =>
|
||||||
pipe(
|
pipe(
|
||||||
execTestScript(script, envs, fakeResponse),
|
runTestScript(script, envs, fakeResponse),
|
||||||
TE.map((x) => x.tests)
|
TE.map((x) => x.tests)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import * as TE from "fp-ts/TaskEither"
|
import * as TE from "fp-ts/TaskEither"
|
||||||
import { pipe } from "fp-ts/function"
|
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 = {
|
const fakeResponse: TestResponse = {
|
||||||
status: 200,
|
status: 200,
|
||||||
@@ -10,13 +12,13 @@ const fakeResponse: TestResponse = {
|
|||||||
|
|
||||||
const func = (script: string, envs: TestResult["envs"]) =>
|
const func = (script: string, envs: TestResult["envs"]) =>
|
||||||
pipe(
|
pipe(
|
||||||
execTestScript(script, envs, fakeResponse),
|
runTestScript(script, envs, fakeResponse),
|
||||||
TE.map((x) => x.envs)
|
TE.map((x) => x.envs)
|
||||||
)
|
)
|
||||||
|
|
||||||
const funcTest = (script: string, envs: TestResult["envs"]) =>
|
const funcTest = (script: string, envs: TestResult["envs"]) =>
|
||||||
pipe(
|
pipe(
|
||||||
execTestScript(script, envs, fakeResponse),
|
runTestScript(script, envs, fakeResponse),
|
||||||
TE.map((x) => x.tests)
|
TE.map((x) => x.tests)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
import "@relmify/jest-fp-ts"
|
||||||
import * as TE from "fp-ts/TaskEither"
|
import * as TE from "fp-ts/TaskEither"
|
||||||
import { pipe } from "fp-ts/function"
|
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 = {
|
const fakeResponse: TestResponse = {
|
||||||
status: 200,
|
status: 200,
|
||||||
@@ -11,7 +13,7 @@ const fakeResponse: TestResponse = {
|
|||||||
|
|
||||||
const func = (script: string, res: TestResponse) =>
|
const func = (script: string, res: TestResponse) =>
|
||||||
pipe(
|
pipe(
|
||||||
execTestScript(script, { global: [], selected: [] }, res),
|
runTestScript(script, { global: [], selected: [] }, res),
|
||||||
TE.map((x) => x.tests)
|
TE.map((x) => x.tests)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
import "@relmify/jest-fp-ts"
|
||||||
import * as TE from "fp-ts/TaskEither"
|
import * as TE from "fp-ts/TaskEither"
|
||||||
import { pipe } from "fp-ts/function"
|
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 = {
|
const fakeResponse: TestResponse = {
|
||||||
status: 200,
|
status: 200,
|
||||||
@@ -11,7 +13,7 @@ const fakeResponse: TestResponse = {
|
|||||||
|
|
||||||
const func = (script: string, res: TestResponse) =>
|
const func = (script: string, res: TestResponse) =>
|
||||||
pipe(
|
pipe(
|
||||||
execTestScript(script, { global: [], selected: [] }, res),
|
runTestScript(script, { global: [], selected: [] }, res),
|
||||||
TE.map((x) => x.tests)
|
TE.map((x) => x.tests)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import * as TE from "fp-ts/TaskEither"
|
import * as TE from "fp-ts/TaskEither"
|
||||||
import { pipe } from "fp-ts/function"
|
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 = {
|
const fakeResponse: TestResponse = {
|
||||||
status: 200,
|
status: 200,
|
||||||
@@ -10,7 +12,7 @@ const fakeResponse: TestResponse = {
|
|||||||
|
|
||||||
const func = (script: string, res: TestResponse) =>
|
const func = (script: string, res: TestResponse) =>
|
||||||
pipe(
|
pipe(
|
||||||
execTestScript(script, { global: [], selected: [] }, res),
|
runTestScript(script, { global: [], selected: [] }, res),
|
||||||
TE.map((x) => x.tests)
|
TE.map((x) => x.tests)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import * as TE from "fp-ts/TaskEither"
|
import * as TE from "fp-ts/TaskEither"
|
||||||
import { pipe } from "fp-ts/function"
|
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 = {
|
const fakeResponse: TestResponse = {
|
||||||
status: 200,
|
status: 200,
|
||||||
@@ -10,7 +12,7 @@ const fakeResponse: TestResponse = {
|
|||||||
|
|
||||||
const func = (script: string, res: TestResponse) =>
|
const func = (script: string, res: TestResponse) =>
|
||||||
pipe(
|
pipe(
|
||||||
execTestScript(script, { global: [], selected: [] }, res),
|
runTestScript(script, { global: [], selected: [] }, res),
|
||||||
TE.map((x) => x.tests)
|
TE.map((x) => x.tests)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import * as TE from "fp-ts/TaskEither"
|
import * as TE from "fp-ts/TaskEither"
|
||||||
import { pipe } from "fp-ts/function"
|
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 = {
|
const fakeResponse: TestResponse = {
|
||||||
status: 200,
|
status: 200,
|
||||||
@@ -10,7 +12,7 @@ const fakeResponse: TestResponse = {
|
|||||||
|
|
||||||
const func = (script: string, res: TestResponse) =>
|
const func = (script: string, res: TestResponse) =>
|
||||||
pipe(
|
pipe(
|
||||||
execTestScript(script, { global: [], selected: [] }, res),
|
runTestScript(script, { global: [], selected: [] }, res),
|
||||||
TE.map((x) => x.tests)
|
TE.map((x) => x.tests)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import * as TE from "fp-ts/TaskEither"
|
import * as TE from "fp-ts/TaskEither"
|
||||||
import { pipe } from "fp-ts/function"
|
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 = {
|
const fakeResponse: TestResponse = {
|
||||||
status: 200,
|
status: 200,
|
||||||
@@ -10,7 +12,7 @@ const fakeResponse: TestResponse = {
|
|||||||
|
|
||||||
const func = (script: string, res: TestResponse) =>
|
const func = (script: string, res: TestResponse) =>
|
||||||
pipe(
|
pipe(
|
||||||
execTestScript(script, { global: [], selected: [] }, res),
|
runTestScript(script, { global: [], selected: [] }, res),
|
||||||
TE.map((x) => x.tests)
|
TE.map((x) => x.tests)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,40 +1,15 @@
|
|||||||
import { match } from "fp-ts/lib/Either"
|
import { preventCyclicObjects } from "~/utils"
|
||||||
import { pipe } from "fp-ts/lib/function"
|
|
||||||
import * as QuickJS from "quickjs-emscripten"
|
|
||||||
import { marshalObjectToVM } from "../utils"
|
|
||||||
|
|
||||||
let vm: QuickJS.QuickJSVm
|
describe("preventCyclicObjects", () => {
|
||||||
|
test("succeeds with a simple object", () => {
|
||||||
beforeAll(async () => {
|
|
||||||
const qjs = await QuickJS.getQuickJS()
|
|
||||||
vm = qjs.createVm()
|
|
||||||
})
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
vm.dispose()
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("marshalObjectToVM", () => {
|
|
||||||
test("successfully marshals simple object into the vm", () => {
|
|
||||||
const testObj = {
|
const testObj = {
|
||||||
a: 1,
|
a: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
const objVMHandle: QuickJS.QuickJSHandle | null = pipe(
|
expect(preventCyclicObjects(testObj)).toBeRight()
|
||||||
marshalObjectToVM(vm, testObj),
|
|
||||||
match(
|
|
||||||
() => null,
|
|
||||||
(result) => result
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(objVMHandle).not.toBeNull()
|
|
||||||
expect(vm.dump(objVMHandle!)).toEqual(testObj)
|
|
||||||
|
|
||||||
objVMHandle!.dispose()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test("fails marshalling cyclic object into vm", () => {
|
test("fails with a cyclic object", () => {
|
||||||
const testObj = {
|
const testObj = {
|
||||||
a: 1,
|
a: 1,
|
||||||
b: null as any,
|
b: null as any,
|
||||||
@@ -42,14 +17,6 @@ describe("marshalObjectToVM", () => {
|
|||||||
|
|
||||||
testObj.b = testObj
|
testObj.b = testObj
|
||||||
|
|
||||||
const objVMHandle: QuickJS.QuickJSHandle | null = pipe(
|
expect(preventCyclicObjects(testObj)).toBeLeft()
|
||||||
marshalObjectToVM(vm, testObj),
|
|
||||||
match(
|
|
||||||
() => null,
|
|
||||||
(result) => result
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(objVMHandle).toBeNull()
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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