Compare commits
51 Commits
feat/realt
...
orphan-pr/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d78e6d209 | ||
|
|
172e459872 | ||
|
|
cfdab014c7 | ||
|
|
ed6e1c0f94 | ||
|
|
07a8a37739 | ||
|
|
ca553b9d3c | ||
|
|
69aaeaf42a | ||
|
|
015393d98f | ||
|
|
c8dec56b96 | ||
|
|
8fefd37862 | ||
|
|
c1cc1ce295 | ||
|
|
16be7c38f3 | ||
|
|
82b6ad935a | ||
|
|
185dc3f2c9 | ||
|
|
51138fa42d | ||
|
|
7f08a4bd81 | ||
|
|
0244b941b3 | ||
|
|
2d0bd48e00 | ||
|
|
15e433b114 | ||
|
|
97ff089110 | ||
|
|
a6b5295df5 | ||
|
|
6b1ca1dce1 | ||
|
|
04a9c4dc52 | ||
|
|
e5e44b889f | ||
|
|
c46bc40bcb | ||
|
|
a91a8ba575 | ||
|
|
1f536eeedd | ||
|
|
25253c4bdf | ||
|
|
043c49541f | ||
|
|
a78462fbe3 | ||
|
|
52c25e497f | ||
|
|
4f539c9781 | ||
|
|
ba468bb835 | ||
|
|
93faa8d5ff | ||
|
|
cf90d16f8a | ||
|
|
39f72f8458 | ||
|
|
5233c36904 | ||
|
|
6417ece710 | ||
|
|
a3d92c862c | ||
|
|
f84b67ec39 | ||
|
|
3afd2c1cf2 | ||
|
|
ed49c0b72c | ||
|
|
bc2f81ff25 | ||
|
|
721a201e7a | ||
|
|
b0071e6859 | ||
|
|
3a41d79c2f | ||
|
|
2fd9eb0767 | ||
|
|
2aef9c5691 | ||
|
|
14e9d19ae6 | ||
|
|
a04b634089 | ||
|
|
2244d38439 |
16
.github/workflows/deploy-netlify.yml
vendored
16
.github/workflows/deploy-netlify.yml
vendored
@@ -12,11 +12,11 @@ jobs:
|
|||||||
- name: Checkout Repository
|
- name: Checkout Repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Setup and run pnpm install
|
||||||
run: curl -f https://get.pnpm.io/v6.14.js | node - add --global pnpm@6
|
uses: pnpm/action-setup@v2.2.2
|
||||||
|
with:
|
||||||
- name: Install Dependencies
|
version: 7
|
||||||
run: pnpm install
|
run_install: true
|
||||||
|
|
||||||
- name: Setup Environment
|
- name: Setup Environment
|
||||||
run: mv packages/hoppscotch-app/.env.example packages/hoppscotch-app/.env
|
run: mv packages/hoppscotch-app/.env.example packages/hoppscotch-app/.env
|
||||||
@@ -24,11 +24,11 @@ jobs:
|
|||||||
- name: Build Site
|
- name: Build Site
|
||||||
run: pnpm run generate
|
run: pnpm run generate
|
||||||
|
|
||||||
# Deploy the site with netlify-cli
|
# Deploy the production site with netlify-cli
|
||||||
- name: Deploy to Netlify
|
- name: Deploy to Netlify (production)
|
||||||
uses: netlify/actions/cli@master
|
uses: netlify/actions/cli@master
|
||||||
env:
|
env:
|
||||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
|
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_PRODUCTION_SITE_ID }}
|
||||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||||
with:
|
with:
|
||||||
args: deploy --dir=packages/hoppscotch-app/dist --prod
|
args: deploy --dir=packages/hoppscotch-app/dist --prod
|
||||||
|
|||||||
45
.github/workflows/deploy-staging-netlify.yml
vendored
Normal file
45
.github/workflows/deploy-staging-netlify.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
name: Deploy to Staging Netlify
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
# TODO: Migrate to staging branch only
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Push build files to Netlify
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout Repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Setup and run pnpm install
|
||||||
|
uses: pnpm/action-setup@v2.2.2
|
||||||
|
with:
|
||||||
|
version: 7
|
||||||
|
run_install: true
|
||||||
|
|
||||||
|
- name: Build Site
|
||||||
|
env:
|
||||||
|
GA_ID: ${{ secrets.STAGING_GA_ID }}
|
||||||
|
GTM_ID: ${{ secrets.STAGING_GTM_ID }}
|
||||||
|
API_KEY: ${{ secrets.STAGING_FB_API_KEY }}
|
||||||
|
AUTH_DOMAIN: ${{ secrets.STAGING_FB_AUTH_DOMAIN }}
|
||||||
|
DATABASE_URL: ${{ secrets.STAGING_FB_DATABASE_URL }}
|
||||||
|
PROJECT_ID: ${{ secrets.STAGING_FB_PROJECT_ID }}
|
||||||
|
STORAGE_BUCKET: ${{ secrets.STAGING_FB_STORAGE_BUCKET }}
|
||||||
|
MESSAGING_SENDER_ID: ${{ secrets.STAGING_FB_MESSAGING_SENDER_ID }}
|
||||||
|
APP_ID: ${{ secrets.STAGING_FB_APP_ID }}
|
||||||
|
BASE_URL: ${{ secrets.STAGING_BASE_URL }}
|
||||||
|
BACKEND_GQL_URL: ${{ secrets.STAGING_BACKEND_GQL_URL }}
|
||||||
|
BACKEND_WS_URL: ${{ secrets.STAGING_BACKEND_WS_URL }}
|
||||||
|
run: pnpm run generate
|
||||||
|
|
||||||
|
# Deploy the staging site with netlify-cli
|
||||||
|
- name: Deploy to Netlify (staging)
|
||||||
|
uses: netlify/actions/cli@master
|
||||||
|
env:
|
||||||
|
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_STAGING_SITE_ID }}
|
||||||
|
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||||
|
with:
|
||||||
|
args: deploy --dir=packages/hoppscotch-app/dist --prod
|
||||||
9
.github/workflows/tests.yml
vendored
9
.github/workflows/tests.yml
vendored
@@ -17,12 +17,15 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v2
|
||||||
- name: Install pnpm
|
- name: Setup and run pnpm install
|
||||||
run: curl -f https://get.pnpm.io/v6.14.js | node - add --global pnpm@6
|
uses: pnpm/action-setup@v2.2.2
|
||||||
|
with:
|
||||||
|
version: 7
|
||||||
|
run_install: true
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: pnpm i && pnpm -r test
|
run: pnpm test
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@hoppscotch/codemirror-lang-graphql",
|
"name": "@hoppscotch/codemirror-lang-graphql",
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"description": "GraphQL language support for CodeMirror",
|
"description": "GraphQL language support for CodeMirror",
|
||||||
"author": "Hoppscotch (support@hoppscotch.io)",
|
"author": "Hoppscotch (support@hoppscotch.io)",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -27,16 +27,22 @@ export const GQLLanguage = LRLanguage.define({
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
styleTags({
|
styleTags({
|
||||||
Name: t.definition(t.variableName),
|
|
||||||
"OperationDefinition/Name": t.definition(t.function(t.variableName)),
|
|
||||||
OperationType: t.keyword,
|
|
||||||
BooleanValue: t.bool,
|
|
||||||
StringValue: t.string,
|
|
||||||
IntValue: t.number,
|
|
||||||
FloatValue: t.number,
|
|
||||||
NullValue: t.null,
|
|
||||||
ObjectValue: t.brace,
|
|
||||||
Comment: t.lineComment,
|
Comment: t.lineComment,
|
||||||
|
Name: t.propertyName,
|
||||||
|
StringValue: t.string,
|
||||||
|
IntValue: t.integer,
|
||||||
|
FloatValue: t.float,
|
||||||
|
NullValue: t.null,
|
||||||
|
BooleanValue: t.bool,
|
||||||
|
Comma: t.separator,
|
||||||
|
"OperationDefinition/Name": t.definition(t.function(t.variableName)),
|
||||||
|
"OperationType TypeKeyword SchemaKeyword FragmentKeyword OnKeyword DirectiveKeyword RepeatableKeyword SchemaKeyword ExtendKeyword ScalarKeyword InterfaceKeyword UnionKeyword EnumKeyword InputKeyword ImplementsKeyword": t.keyword,
|
||||||
|
"ExecutableDirectiveLocation TypeSystemDirectiveLocation": t.atom,
|
||||||
|
"DirectiveName!": t.annotation,
|
||||||
|
"\"{\" \"}\"": t.brace,
|
||||||
|
"\"(\" \")\"": t.paren,
|
||||||
|
"\"[\" \"]\"": t.squareBracket,
|
||||||
|
"Type! NamedType": t.typeName,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -33,16 +33,24 @@ TypeSystemExtension {
|
|||||||
TypeExtension
|
TypeExtension
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SchemaKeyword {
|
||||||
|
@specialize<Name, "schema">
|
||||||
|
}
|
||||||
|
|
||||||
SchemaDefinition {
|
SchemaDefinition {
|
||||||
Description? @specialize<Name, "schema"> Directives? RootTypeDef
|
Description? SchemaKeyword Directives? RootTypeDef
|
||||||
}
|
}
|
||||||
|
|
||||||
RootTypeDef {
|
RootTypeDef {
|
||||||
"{" RootOperationTypeDefinition+ "}"
|
"{" RootOperationTypeDefinition+ "}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ExtendKeyword {
|
||||||
|
@specialize<Name, "extend">
|
||||||
|
}
|
||||||
|
|
||||||
SchemaExtension {
|
SchemaExtension {
|
||||||
@specialize<Name, "extend"> @specialize<Name, "schema"> Directives? RootTypeDef
|
ExtendKeyword SchemaKeyword Directives? RootTypeDef
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeExtension {
|
TypeExtension {
|
||||||
@@ -54,33 +62,53 @@ TypeExtension {
|
|||||||
InputObjectTypeExtension
|
InputObjectTypeExtension
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ScalarKeyword {
|
||||||
|
@specialize<Name, "scalar">
|
||||||
|
}
|
||||||
|
|
||||||
ScalarTypeExtension {
|
ScalarTypeExtension {
|
||||||
@specialize<Name, "extend"> @specialize<Name, "scalar"> Name Directives
|
ExtendKeyword ScalarKeyword Name Directives
|
||||||
}
|
}
|
||||||
|
|
||||||
ObjectTypeExtension /* precedence: right 0 */ {
|
ObjectTypeExtension /* precedence: right 0 */ {
|
||||||
@specialize<Name, "extend"> @specialize<Name, "type"> Name ImplementsInterfaces? Directives? !typeDef FieldsDefinition |
|
ExtendKeyword TypeKeyword Name ImplementsInterfaces? Directives? !typeDef FieldsDefinition |
|
||||||
@specialize<Name, "extend"> @specialize<Name, "type"> Name ImplementsInterfaces? Directives?
|
ExtendKeyword TypeKeyword Name ImplementsInterfaces? Directives?
|
||||||
|
}
|
||||||
|
|
||||||
|
InterfaceKeyword {
|
||||||
|
@specialize<Name, "interface">
|
||||||
}
|
}
|
||||||
|
|
||||||
InterfaceTypeExtension /* precedence: right 0 */ {
|
InterfaceTypeExtension /* precedence: right 0 */ {
|
||||||
@specialize<Name, "extend"> @specialize<Name, "interface"> Name ImplementsInterfaces? Directives? FieldsDefinition |
|
ExtendKeyword InterfaceKeyword Name ImplementsInterfaces? Directives? FieldsDefinition |
|
||||||
@specialize<Name, "extend"> @specialize<Name, "interface"> Name ImplementsInterfaces? Directives?
|
ExtendKeyword InterfaceKeyword Name ImplementsInterfaces? Directives?
|
||||||
|
}
|
||||||
|
|
||||||
|
UnionKeyword {
|
||||||
|
@specialize<Name, "union">
|
||||||
}
|
}
|
||||||
|
|
||||||
UnionTypeExtension /* precedence: right 0 */ {
|
UnionTypeExtension /* precedence: right 0 */ {
|
||||||
@specialize<Name, "extend"> @specialize<Name, "union"> Name Directives? UnionMemberTypes |
|
ExtendKeyword UnionKeyword Name Directives? UnionMemberTypes |
|
||||||
@specialize<Name, "extend"> @specialize<Name, "union"> Name Directives?
|
ExtendKeyword UnionKeyword Name Directives?
|
||||||
|
}
|
||||||
|
|
||||||
|
EnumKeyword {
|
||||||
|
@specialize<Name, "enum">
|
||||||
}
|
}
|
||||||
|
|
||||||
EnumTypeExtension /* precedence: right 0 */ {
|
EnumTypeExtension /* precedence: right 0 */ {
|
||||||
@specialize<Name, "extend"> @specialize<Name, "enum"> Name Directives? !typeDef EnumValuesDefinition |
|
ExtendKeyword EnumKeyword Name Directives? !typeDef EnumValuesDefinition |
|
||||||
@specialize<Name, "extend"> @specialize<Name, "enum"> Name Directives?
|
ExtendKeyword EnumKeyword Name Directives?
|
||||||
|
}
|
||||||
|
|
||||||
|
InputKeyword {
|
||||||
|
@specialize<Name, "input">
|
||||||
}
|
}
|
||||||
|
|
||||||
InputObjectTypeExtension /* precedence: right 0 */ {
|
InputObjectTypeExtension /* precedence: right 0 */ {
|
||||||
@specialize<Name, "extend"> @specialize<Name, "input"> Name Directives? InputFieldsDefinition+ |
|
ExtendKeyword InputKeyword Name Directives? InputFieldsDefinition+ |
|
||||||
@specialize<Name, "extend"> @specialize<Name, "input"> Name Directives?
|
ExtendKeyword InputKeyword Name Directives?
|
||||||
}
|
}
|
||||||
|
|
||||||
InputFieldsDefinition {
|
InputFieldsDefinition {
|
||||||
@@ -95,9 +123,13 @@ EnumValueDefinition {
|
|||||||
Description? EnumValue Directives?
|
Description? EnumValue Directives?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImplementsKeyword {
|
||||||
|
@specialize<Name, "implements">
|
||||||
|
}
|
||||||
|
|
||||||
ImplementsInterfaces {
|
ImplementsInterfaces {
|
||||||
ImplementsInterfaces "&" NamedType |
|
ImplementsInterfaces "&" NamedType |
|
||||||
@specialize<Name, "implements"> "&"? NamedType
|
ImplementsKeyword "&"? NamedType
|
||||||
}
|
}
|
||||||
|
|
||||||
FieldsDefinition {
|
FieldsDefinition {
|
||||||
@@ -144,27 +176,31 @@ TypeDefinition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ScalarTypeDefinition /* precedence: right 0 */ {
|
ScalarTypeDefinition /* precedence: right 0 */ {
|
||||||
Description? @specialize<Name, "scalar"> Name Directives?
|
Description? ScalarKeyword Name Directives?
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeKeyword {
|
||||||
|
@specialize<Name, "type">
|
||||||
}
|
}
|
||||||
|
|
||||||
ObjectTypeDefinition /* precedence: right 0 */ {
|
ObjectTypeDefinition /* precedence: right 0 */ {
|
||||||
Description? @specialize<Name, "type"> Name ImplementsInterfaces? Directives? FieldsDefinition?
|
Description? TypeKeyword Name ImplementsInterfaces? Directives? FieldsDefinition?
|
||||||
}
|
}
|
||||||
|
|
||||||
InterfaceTypeDefinition /* precedence: right 0 */ {
|
InterfaceTypeDefinition /* precedence: right 0 */ {
|
||||||
Description? @specialize<Name, "interface"> Name ImplementsInterfaces? Directives? FieldsDefinition?
|
Description? InterfaceKeyword Name ImplementsInterfaces? Directives? FieldsDefinition?
|
||||||
}
|
}
|
||||||
|
|
||||||
UnionTypeDefinition /* precedence: right 0 */ {
|
UnionTypeDefinition /* precedence: right 0 */ {
|
||||||
Description? @specialize<Name, "union"> Name Directives? UnionMemberTypes?
|
Description? UnionKeyword Name Directives? UnionMemberTypes?
|
||||||
}
|
}
|
||||||
|
|
||||||
EnumTypeDefinition /* precedence: right 0 */ {
|
EnumTypeDefinition /* precedence: right 0 */ {
|
||||||
Description? @specialize<Name, "enum"> Name Directives? !typeDef EnumValuesDefinition?
|
Description? EnumKeyword Name Directives? !typeDef EnumValuesDefinition?
|
||||||
}
|
}
|
||||||
|
|
||||||
InputObjectTypeDefinition /* precedence: right 0 */ {
|
InputObjectTypeDefinition /* precedence: right 0 */ {
|
||||||
Description? @specialize<Name, "input"> Name Directives? !typeDef InputFieldsDefinition?
|
Description? InputKeyword Name Directives? !typeDef InputFieldsDefinition?
|
||||||
}
|
}
|
||||||
|
|
||||||
VariableDefinitions {
|
VariableDefinitions {
|
||||||
@@ -237,8 +273,12 @@ FragmentSpread {
|
|||||||
"..." FragmentName Directives?
|
"..." FragmentName Directives?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FragmentKeyword {
|
||||||
|
@specialize<Name, "fragment">
|
||||||
|
}
|
||||||
|
|
||||||
FragmentDefinition {
|
FragmentDefinition {
|
||||||
@specialize<Name, "fragment"> FragmentName TypeCondition Directives? SelectionSet
|
FragmentKeyword FragmentName TypeCondition Directives? SelectionSet
|
||||||
}
|
}
|
||||||
|
|
||||||
FragmentName {
|
FragmentName {
|
||||||
@@ -249,20 +289,36 @@ InlineFragment {
|
|||||||
"..." TypeCondition? Directives? SelectionSet
|
"..." TypeCondition? Directives? SelectionSet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OnKeyword {
|
||||||
|
@specialize<Name, "on">
|
||||||
|
}
|
||||||
|
|
||||||
TypeCondition {
|
TypeCondition {
|
||||||
@specialize<Name, "on"> NamedType
|
OnKeyword NamedType
|
||||||
}
|
}
|
||||||
|
|
||||||
Directives {
|
Directives {
|
||||||
Directive+
|
Directive+
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DirectiveName {
|
||||||
|
"@" Name
|
||||||
|
}
|
||||||
|
|
||||||
Directive {
|
Directive {
|
||||||
"@" Name Arguments?
|
DirectiveName Arguments?
|
||||||
|
}
|
||||||
|
|
||||||
|
DirectiveKeyword {
|
||||||
|
@specialize<Name, "directive">
|
||||||
|
}
|
||||||
|
|
||||||
|
RepeatableKeyword {
|
||||||
|
@specialize<Name, "repeatable">
|
||||||
}
|
}
|
||||||
|
|
||||||
DirectiveDefinition /* precedence: right 1 */ {
|
DirectiveDefinition /* precedence: right 1 */ {
|
||||||
Description? @specialize<Name, "directive"> "@" Name ArgumentsDefinition? @specialize<Name, "repeatable"> ? @specialize<Name, "on"> DirectiveLocations
|
Description? DirectiveKeyword "@" Name ArgumentsDefinition? RepeatableKeyword ? OnKeyword DirectiveLocations
|
||||||
}
|
}
|
||||||
|
|
||||||
DirectiveLocations {
|
DirectiveLocations {
|
||||||
@@ -338,17 +394,14 @@ TypeSystemDirectiveLocation {
|
|||||||
| @specialize<Name, "INPUT_FIELD_DEFINITION">
|
| @specialize<Name, "INPUT_FIELD_DEFINITION">
|
||||||
}
|
}
|
||||||
|
|
||||||
@skip { whitespace | Comment }
|
|
||||||
|
|
||||||
@tokens {
|
@tokens {
|
||||||
whitespace {
|
whitespace {
|
||||||
std.whitespace+
|
std.whitespace+
|
||||||
}
|
}
|
||||||
|
|
||||||
StringValue {
|
StringValue {
|
||||||
"\"\"\"" (!["] | "\\n" | "\"" "\""? !["])* "\"\"\"" | "\"" !["\\\n]* "\""
|
"\"\"\"" (!["] | "\\n" | "\"" "\""? !["])* "\"\"\"" | "\"" !["\\\n]* "\""
|
||||||
}
|
}
|
||||||
|
|
||||||
IntValue {
|
IntValue {
|
||||||
"-"? "0"
|
"-"? "0"
|
||||||
| "-"? std.digit+
|
| "-"? std.digit+
|
||||||
@@ -363,14 +416,19 @@ TypeSystemDirectiveLocation {
|
|||||||
Name {
|
Name {
|
||||||
$[_A-Za-z] $[_0-9A-Za-z]*
|
$[_A-Za-z] $[_0-9A-Za-z]*
|
||||||
}
|
}
|
||||||
Comment {
|
|
||||||
"#" ![\n]*
|
|
||||||
}
|
|
||||||
Comma {
|
Comma {
|
||||||
","
|
","
|
||||||
}
|
}
|
||||||
|
|
||||||
"{" "}" "[" "]"
|
Comment {
|
||||||
|
"#" ![\n]*
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
"{" "}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@skip { whitespace | Comment }
|
||||||
|
|
||||||
@detectDelim
|
@detectDelim
|
||||||
|
|||||||
@@ -16,3 +16,7 @@ MEASUREMENT_ID=G-BBJ3R80PJT
|
|||||||
|
|
||||||
# Base URL
|
# Base URL
|
||||||
BASE_URL=https://hoppscotch.io
|
BASE_URL=https://hoppscotch.io
|
||||||
|
|
||||||
|
# Backend URLs
|
||||||
|
BACKEND_GQL_URL=https://api.hoppscotch.io/graphql
|
||||||
|
BACKEND_WS_URL=wss://api.hoppscotch.io/graphql
|
||||||
|
|||||||
13
packages/hoppscotch-app/assets/icons/filter.svg
Normal file
13
packages/hoppscotch-app/assets/icons/filter.svg
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"></polygon>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 283 B |
@@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
::-webkit-scrollbar-track {
|
::-webkit-scrollbar-track {
|
||||||
@apply bg-transparent;
|
@apply bg-transparent;
|
||||||
|
@apply border-solid border-l border-t-0 border-b-0 border-r-0 border-dividerLight;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
@@ -27,17 +28,17 @@
|
|||||||
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
@apply w-4;
|
@apply w-4;
|
||||||
@apply h-4;
|
@apply h-0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hide-scrollbar {
|
// .hide-scrollbar {
|
||||||
-ms-overflow-style: none;
|
// -ms-overflow-style: none;
|
||||||
scrollbar-width: none;
|
// scrollbar-width: none;
|
||||||
}
|
// }
|
||||||
|
|
||||||
.hide-scrollbar::-webkit-scrollbar {
|
// .hide-scrollbar::-webkit-scrollbar {
|
||||||
@apply hidden;
|
// @apply hidden;
|
||||||
}
|
// }
|
||||||
|
|
||||||
input::placeholder,
|
input::placeholder,
|
||||||
textarea::placeholder,
|
textarea::placeholder,
|
||||||
|
|||||||
@@ -255,6 +255,7 @@
|
|||||||
--upper-mobile-raw-tertiary-sticky-fold: 8.188rem;
|
--upper-mobile-raw-tertiary-sticky-fold: 8.188rem;
|
||||||
--lower-primary-sticky-fold: 3rem;
|
--lower-primary-sticky-fold: 3rem;
|
||||||
--lower-secondary-sticky-fold: 5rem;
|
--lower-secondary-sticky-fold: 5rem;
|
||||||
|
--lower-tertiary-sticky-fold: 7.05rem;
|
||||||
--sidebar-primary-sticky-fold: 2rem;
|
--sidebar-primary-sticky-fold: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,6 +271,7 @@
|
|||||||
--upper-mobile-raw-tertiary-sticky-fold: 8.938rem;
|
--upper-mobile-raw-tertiary-sticky-fold: 8.938rem;
|
||||||
--lower-primary-sticky-fold: 3.25rem;
|
--lower-primary-sticky-fold: 3.25rem;
|
||||||
--lower-secondary-sticky-fold: 5.5rem;
|
--lower-secondary-sticky-fold: 5.5rem;
|
||||||
|
--lower-tertiary-sticky-fold: 7.8rem;
|
||||||
--sidebar-primary-sticky-fold: 2.25rem;
|
--sidebar-primary-sticky-fold: 2.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,6 +287,7 @@
|
|||||||
--upper-mobile-raw-tertiary-sticky-fold: 9.688rem;
|
--upper-mobile-raw-tertiary-sticky-fold: 9.688rem;
|
||||||
--lower-primary-sticky-fold: 3.5rem;
|
--lower-primary-sticky-fold: 3.5rem;
|
||||||
--lower-secondary-sticky-fold: 6rem;
|
--lower-secondary-sticky-fold: 6rem;
|
||||||
|
--lower-tertiary-sticky-fold: 8.55rem;
|
||||||
--sidebar-primary-sticky-fold: 2.5rem;
|
--sidebar-primary-sticky-fold: 2.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "@nuxtjs/composition-api"
|
import { refAutoReset } from "@vueuse/core"
|
||||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
||||||
import {
|
import {
|
||||||
useI18n,
|
useI18n,
|
||||||
@@ -45,7 +45,7 @@ const emit = defineEmits<{
|
|||||||
(e: "hide-modal"): void
|
(e: "hide-modal"): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const copyIcon = ref("copy")
|
const copyIcon = refAutoReset<"copy" | "check">("copy", 1000)
|
||||||
|
|
||||||
// Copy user auth token to clipboard
|
// Copy user auth token to clipboard
|
||||||
const copyUserAuthToken = () => {
|
const copyUserAuthToken = () => {
|
||||||
@@ -53,7 +53,6 @@ const copyUserAuthToken = () => {
|
|||||||
copyToClipboard(userAuthToken.value)
|
copyToClipboard(userAuthToken.value)
|
||||||
copyIcon.value = "check"
|
copyIcon.value = "check"
|
||||||
toast.success(`${t("state.copied_to_clipboard")}`)
|
toast.success(`${t("state.copied_to_clipboard")}`)
|
||||||
setTimeout(() => (copyIcon.value = "copy"), 1000)
|
|
||||||
} else {
|
} else {
|
||||||
toast.error(`${t("error.something_went_wrong")}`)
|
toast.error(`${t("error.something_went_wrong")}`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<header
|
<header
|
||||||
class="flex items-center justify-between flex-1 px-2 py-2 space-x-2"
|
class="flex items-center justify-between flex-1 px-2 py-2 space-x-2 overflow-x-auto"
|
||||||
>
|
>
|
||||||
<div class="inline-flex items-center space-x-2">
|
<div class="inline-flex items-center space-x-2">
|
||||||
<ButtonSecondary
|
<ButtonSecondary
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "@nuxtjs/composition-api"
|
import { refAutoReset } from "@vueuse/core"
|
||||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
||||||
import { useI18n, useToast } from "~/helpers/utils/composables"
|
import { useI18n, useToast } from "~/helpers/utils/composables"
|
||||||
|
|
||||||
@@ -60,7 +60,8 @@ const subject = "Checkout Hoppscotch - an open source API development ecosystem"
|
|||||||
const summary = `Hi there!%0D%0A%0D%0AI thought you'll like this new platform that I joined called Hoppscotch - https://hoppscotch.io.%0D%0AIt is a simple and intuitive interface for creating and managing your APIs. You can build, test, document, and share your APIs.%0D%0A%0D%0AThe best part about Hoppscotch is that it is open source and free to get started.%0D%0A%0D%0A`
|
const summary = `Hi there!%0D%0A%0D%0AI thought you'll like this new platform that I joined called Hoppscotch - https://hoppscotch.io.%0D%0AIt is a simple and intuitive interface for creating and managing your APIs. You can build, test, document, and share your APIs.%0D%0A%0D%0AThe best part about Hoppscotch is that it is open source and free to get started.%0D%0A%0D%0A`
|
||||||
const twitter = "hoppscotch_io"
|
const twitter = "hoppscotch_io"
|
||||||
|
|
||||||
const copyIcon = ref("copy")
|
const copyIcon = refAutoReset<"copy" | "check">("copy", 1000)
|
||||||
|
|
||||||
const platforms = [
|
const platforms = [
|
||||||
{
|
{
|
||||||
name: "Email",
|
name: "Email",
|
||||||
@@ -93,7 +94,6 @@ const copyAppLink = () => {
|
|||||||
copyToClipboard(url)
|
copyToClipboard(url)
|
||||||
copyIcon.value = "check"
|
copyIcon.value = "check"
|
||||||
toast.success(`${t("state.copied_to_clipboard")}`)
|
toast.success(`${t("state.copied_to_clipboard")}`)
|
||||||
setTimeout(() => (copyIcon.value = "copy"), 1000)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const hideModal = () => {
|
const hideModal = () => {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
:to="localePath(navigation.target)"
|
:to="localePath(navigation.target)"
|
||||||
class="nav-link"
|
class="nav-link"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
|
:exact="navigation.exact"
|
||||||
>
|
>
|
||||||
<div v-if="navigation.svg">
|
<div v-if="navigation.svg">
|
||||||
<SmartIcon :name="navigation.svg" class="svg-icons" />
|
<SmartIcon :name="navigation.svg" class="svg-icons" />
|
||||||
@@ -40,26 +41,31 @@ const primaryNavigation = [
|
|||||||
target: "index",
|
target: "index",
|
||||||
svg: "link-2",
|
svg: "link-2",
|
||||||
title: t("navigation.rest"),
|
title: t("navigation.rest"),
|
||||||
|
exact: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
target: "graphql",
|
target: "graphql",
|
||||||
svg: "graphql",
|
svg: "graphql",
|
||||||
title: t("navigation.graphql"),
|
title: t("navigation.graphql"),
|
||||||
|
exact: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
target: "realtime",
|
target: "realtime",
|
||||||
svg: "globe",
|
svg: "globe",
|
||||||
title: t("navigation.realtime"),
|
title: t("navigation.realtime"),
|
||||||
|
exact: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
target: "documentation",
|
target: "documentation",
|
||||||
svg: "book-open",
|
svg: "book-open",
|
||||||
title: t("navigation.doc"),
|
title: t("navigation.doc"),
|
||||||
|
exact: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
target: "settings",
|
target: "settings",
|
||||||
svg: "settings",
|
svg: "settings",
|
||||||
title: t("navigation.settings"),
|
title: t("navigation.settings"),
|
||||||
|
exact: false,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
</script>
|
</script>
|
||||||
@@ -105,6 +111,20 @@ const primaryNavigation = [
|
|||||||
@apply text-tiny;
|
@apply text-tiny;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.active-link {
|
||||||
|
@apply text-secondaryDark;
|
||||||
|
@apply bg-primaryLight;
|
||||||
|
@apply hover:text-secondaryDark;
|
||||||
|
|
||||||
|
.material-icons,
|
||||||
|
.svg-icons {
|
||||||
|
@apply opacity-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
@apply bg-accent;
|
||||||
|
}
|
||||||
|
}
|
||||||
&.exact-active-link {
|
&.exact-active-link {
|
||||||
@apply text-secondaryDark;
|
@apply text-secondaryDark;
|
||||||
@apply bg-primaryLight;
|
@apply bg-primaryLight;
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="show">
|
<div v-if="show">
|
||||||
<SmartTabs :id="'collections_tab'" v-model="selectedCollectionTab">
|
<SmartTabs
|
||||||
|
:id="'collections_tab'"
|
||||||
|
v-model="selectedCollectionTab"
|
||||||
|
render-inactive-tabs
|
||||||
|
>
|
||||||
<SmartTab
|
<SmartTab
|
||||||
:id="'my-collections'"
|
:id="'my-collections'"
|
||||||
:label="`${$t('collection.my_collections')}`"
|
:label="`${$t('collection.my_collections')}`"
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ const createCollectionGist = async () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
getJSONCollection()
|
await getJSONCollection()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await axios.$post(
|
const res = await axios.$post(
|
||||||
@@ -316,8 +316,8 @@ const importToTeams = async (content: HoppCollection<HoppRESTRequest>) => {
|
|||||||
importingMyCollections.value = false
|
importingMyCollections.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const exportJSON = () => {
|
const exportJSON = async () => {
|
||||||
getJSONCollection()
|
await getJSONCollection()
|
||||||
|
|
||||||
const dataToWrite = collectionJson.value
|
const dataToWrite = collectionJson.value
|
||||||
const file = new Blob([dataToWrite], { type: "application/json" })
|
const file = new Blob([dataToWrite], { type: "application/json" })
|
||||||
|
|||||||
@@ -322,11 +322,10 @@ const setRestReq = (request: any) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Loads request from the save once, checks for unsaved changes, but ignores default values */
|
||||||
const selectRequest = () => {
|
const selectRequest = () => {
|
||||||
if (!active.value) {
|
// Check if this is a save as request popup, if so we don't need to prompt the confirm change popup.
|
||||||
confirmChange.value = true
|
if (props.saveRequest) {
|
||||||
|
|
||||||
if (props.saveRequest)
|
|
||||||
emit("select", {
|
emit("select", {
|
||||||
picked: {
|
picked: {
|
||||||
pickedType: "my-request",
|
pickedType: "my-request",
|
||||||
@@ -336,6 +335,11 @@ const selectRequest = () => {
|
|||||||
requestIndex: props.requestIndex,
|
requestIndex: props.requestIndex,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
} else if (isEqualHoppRESTRequest(props.request, getDefaultRESTRequest())) {
|
||||||
|
confirmChange.value = false
|
||||||
|
setRestReq(props.request)
|
||||||
|
} else if (!active.value) {
|
||||||
|
confirmChange.value = true
|
||||||
} else {
|
} else {
|
||||||
const currentReqWithNoChange = active.value.req
|
const currentReqWithNoChange = active.value.req
|
||||||
const currentFullReq = getRESTRequest()
|
const currentFullReq = getRESTRequest()
|
||||||
@@ -345,16 +349,6 @@ const selectRequest = () => {
|
|||||||
// Check if there is any changes done on the current request
|
// Check if there is any changes done on the current request
|
||||||
if (isEqualHoppRESTRequest(currentReqWithNoChange, currentFullReq)) {
|
if (isEqualHoppRESTRequest(currentReqWithNoChange, currentFullReq)) {
|
||||||
setRestReq(props.request)
|
setRestReq(props.request)
|
||||||
if (props.saveRequest)
|
|
||||||
emit("select", {
|
|
||||||
picked: {
|
|
||||||
pickedType: "my-request",
|
|
||||||
collectionIndex: props.collectionIndex,
|
|
||||||
folderPath: props.folderPath,
|
|
||||||
folderName: props.folderName,
|
|
||||||
requestIndex: props.requestIndex,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
confirmChange.value = true
|
confirmChange.value = true
|
||||||
}
|
}
|
||||||
@@ -374,16 +368,6 @@ const saveRequestChange = () => {
|
|||||||
/** Discard changes and change the current request and context */
|
/** Discard changes and change the current request and context */
|
||||||
const discardRequestChange = () => {
|
const discardRequestChange = () => {
|
||||||
setRestReq(props.request)
|
setRestReq(props.request)
|
||||||
if (props.saveRequest)
|
|
||||||
emit("select", {
|
|
||||||
picked: {
|
|
||||||
pickedType: "my-request",
|
|
||||||
collectionIndex: props.collectionIndex,
|
|
||||||
folderPath: props.folderPath,
|
|
||||||
folderName: props.folderName,
|
|
||||||
requestIndex: props.requestIndex,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if (!isActive.value) {
|
if (!isActive.value) {
|
||||||
setRESTSaveContext({
|
setRESTSaveContext({
|
||||||
originLocation: "user-collection",
|
originLocation: "user-collection",
|
||||||
|
|||||||
@@ -261,7 +261,7 @@ const active = useReadonlyStream(restSaveContext$, null)
|
|||||||
const isSelected = computed(
|
const isSelected = computed(
|
||||||
() =>
|
() =>
|
||||||
props.picked &&
|
props.picked &&
|
||||||
props.picked.pickedType === "team-collection" &&
|
props.picked.pickedType === "teams-collection" &&
|
||||||
props.picked.requestID === props.requestIndex
|
props.picked.requestID === props.requestIndex
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -308,16 +308,19 @@ const setRestReq = (request: HoppRESTRequest) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const selectRequest = () => {
|
const selectRequest = () => {
|
||||||
if (!active.value) {
|
// Check if this is a save as request popup, if so we don't need to prompt the confirm change popup.
|
||||||
confirmChange.value = true
|
if (props.saveRequest) {
|
||||||
|
|
||||||
if (props.saveRequest)
|
|
||||||
emit("select", {
|
emit("select", {
|
||||||
picked: {
|
picked: {
|
||||||
pickedType: "team-collection",
|
pickedType: "teams-collection",
|
||||||
requestID: props.requestIndex,
|
requestID: props.requestIndex,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
} else if (isEqualHoppRESTRequest(props.request, getDefaultRESTRequest())) {
|
||||||
|
confirmChange.value = false
|
||||||
|
setRestReq(props.request)
|
||||||
|
} else if (!active.value) {
|
||||||
|
confirmChange.value = true
|
||||||
} else {
|
} else {
|
||||||
const currentReqWithNoChange = active.value.req
|
const currentReqWithNoChange = active.value.req
|
||||||
const currentFullReq = getRESTRequest()
|
const currentFullReq = getRESTRequest()
|
||||||
@@ -327,13 +330,6 @@ const selectRequest = () => {
|
|||||||
// Check if there is any changes done on the current request
|
// Check if there is any changes done on the current request
|
||||||
if (isEqualHoppRESTRequest(currentReqWithNoChange, currentFullReq)) {
|
if (isEqualHoppRESTRequest(currentReqWithNoChange, currentFullReq)) {
|
||||||
setRestReq(props.request)
|
setRestReq(props.request)
|
||||||
if (props.saveRequest)
|
|
||||||
emit("select", {
|
|
||||||
picked: {
|
|
||||||
pickedType: "team-collection",
|
|
||||||
requestID: props.requestIndex,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
confirmChange.value = true
|
confirmChange.value = true
|
||||||
}
|
}
|
||||||
@@ -353,13 +349,6 @@ const saveRequestChange = () => {
|
|||||||
/** Discard changes and change the current request and context */
|
/** Discard changes and change the current request and context */
|
||||||
const discardRequestChange = () => {
|
const discardRequestChange = () => {
|
||||||
setRestReq(props.request)
|
setRestReq(props.request)
|
||||||
if (props.saveRequest)
|
|
||||||
emit("select", {
|
|
||||||
picked: {
|
|
||||||
pickedType: "team-collection",
|
|
||||||
requestID: props.requestIndex,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if (!isActive.value) {
|
if (!isActive.value) {
|
||||||
setRESTSaveContext({
|
setRESTSaveContext({
|
||||||
originLocation: "team-collection",
|
originLocation: "team-collection",
|
||||||
@@ -367,7 +356,6 @@ const discardRequestChange = () => {
|
|||||||
req: props.request,
|
req: props.request,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
confirmChange.value = false
|
confirmChange.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -120,10 +120,11 @@ import clone from "lodash/clone"
|
|||||||
import { computed, ref, watch } from "@nuxtjs/composition-api"
|
import { computed, ref, watch } from "@nuxtjs/composition-api"
|
||||||
import * as E from "fp-ts/Either"
|
import * as E from "fp-ts/Either"
|
||||||
import { Environment, parseTemplateStringE } from "@hoppscotch/data"
|
import { Environment, parseTemplateStringE } from "@hoppscotch/data"
|
||||||
|
import { refAutoReset } from "@vueuse/core"
|
||||||
import {
|
import {
|
||||||
createEnvironment,
|
createEnvironment,
|
||||||
environments$,
|
environments$,
|
||||||
getEnviroment,
|
getEnvironment,
|
||||||
getGlobalVariables,
|
getGlobalVariables,
|
||||||
globalEnv$,
|
globalEnv$,
|
||||||
setCurrentEnvironment,
|
setCurrentEnvironment,
|
||||||
@@ -160,7 +161,8 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
const name = ref<string | null>(null)
|
const name = ref<string | null>(null)
|
||||||
const vars = ref([{ key: "", value: "" }])
|
const vars = ref([{ key: "", value: "" }])
|
||||||
const clearIcon = ref("trash-2")
|
|
||||||
|
const clearIcon = refAutoReset<"trash-2" | "check">("trash-2", 1000)
|
||||||
|
|
||||||
const globalVars = useReadonlyStream(globalEnv$, [])
|
const globalVars = useReadonlyStream(globalEnv$, [])
|
||||||
|
|
||||||
@@ -176,7 +178,7 @@ const workingEnv = computed(() => {
|
|||||||
variables: props.envVars(),
|
variables: props.envVars(),
|
||||||
}
|
}
|
||||||
} else if (props.editingEnvironmentIndex !== null) {
|
} else if (props.editingEnvironmentIndex !== null) {
|
||||||
return getEnviroment(props.editingEnvironmentIndex)
|
return getEnvironment(props.editingEnvironmentIndex)
|
||||||
} else {
|
} else {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -225,7 +227,6 @@ const clearContent = () => {
|
|||||||
vars.value = []
|
vars.value = []
|
||||||
clearIcon.value = "check"
|
clearIcon.value = "check"
|
||||||
toast.success(`${t("state.cleared")}`)
|
toast.success(`${t("state.cleared")}`)
|
||||||
setTimeout(() => (clearIcon.value = "trash-2"), 1000)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const addEnvironmentVariable = () => {
|
const addEnvironmentVariable = () => {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<SmartTabs
|
<SmartTabs
|
||||||
v-model="selectedOptionTab"
|
v-model="selectedOptionTab"
|
||||||
styles="sticky bg-primary top-upperPrimaryStickyFold z-10"
|
styles="sticky bg-primary top-upperPrimaryStickyFold z-10"
|
||||||
|
render-inactive-tabs
|
||||||
>
|
>
|
||||||
<SmartTab
|
<SmartTab
|
||||||
:id="'query'"
|
:id="'query'"
|
||||||
@@ -312,6 +313,7 @@ import {
|
|||||||
import draggable from "vuedraggable"
|
import draggable from "vuedraggable"
|
||||||
import isEqual from "lodash/isEqual"
|
import isEqual from "lodash/isEqual"
|
||||||
import cloneDeep from "lodash/cloneDeep"
|
import cloneDeep from "lodash/cloneDeep"
|
||||||
|
import { refAutoReset } from "@vueuse/core"
|
||||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
||||||
import {
|
import {
|
||||||
useNuxt,
|
useNuxt,
|
||||||
@@ -612,10 +614,13 @@ useCodemirror(queryEditor, gqlQueryString, {
|
|||||||
environmentHighlights: false,
|
environmentHighlights: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
const copyQueryIcon = ref("copy")
|
const copyQueryIcon = refAutoReset<"copy" | "check">("copy", 1000)
|
||||||
const copyVariablesIcon = ref("copy")
|
const copyVariablesIcon = refAutoReset<"copy" | "check">("copy", 1000)
|
||||||
const prettifyQueryIcon = ref("wand")
|
const prettifyQueryIcon = refAutoReset<"wand" | "check" | "info">("wand", 1000)
|
||||||
const prettifyVariablesIcon = ref("wand")
|
const prettifyVariablesIcon = refAutoReset<"wand" | "check" | "info">(
|
||||||
|
"wand",
|
||||||
|
1000
|
||||||
|
)
|
||||||
|
|
||||||
const showSaveRequestModal = ref(false)
|
const showSaveRequestModal = ref(false)
|
||||||
|
|
||||||
@@ -623,7 +628,6 @@ const copyQuery = () => {
|
|||||||
copyToClipboard(gqlQueryString.value)
|
copyToClipboard(gqlQueryString.value)
|
||||||
copyQueryIcon.value = "check"
|
copyQueryIcon.value = "check"
|
||||||
toast.success(`${t("state.copied_to_clipboard")}`)
|
toast.success(`${t("state.copied_to_clipboard")}`)
|
||||||
setTimeout(() => (copyQueryIcon.value = "copy"), 1000)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = useStream(gqlResponse$, "", setGQLResponse)
|
const response = useStream(gqlResponse$, "", setGQLResponse)
|
||||||
@@ -699,7 +703,6 @@ const prettifyQuery = () => {
|
|||||||
toast.error(`${t("error.gql_prettify_invalid_query")}`)
|
toast.error(`${t("error.gql_prettify_invalid_query")}`)
|
||||||
prettifyQueryIcon.value = "info"
|
prettifyQueryIcon.value = "info"
|
||||||
}
|
}
|
||||||
setTimeout(() => (prettifyQueryIcon.value = "wand"), 1000)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveRequest = () => {
|
const saveRequest = () => {
|
||||||
@@ -710,7 +713,6 @@ const copyVariables = () => {
|
|||||||
copyToClipboard(variableString.value)
|
copyToClipboard(variableString.value)
|
||||||
copyVariablesIcon.value = "check"
|
copyVariablesIcon.value = "check"
|
||||||
toast.success(`${t("state.copied_to_clipboard")}`)
|
toast.success(`${t("state.copied_to_clipboard")}`)
|
||||||
setTimeout(() => (copyVariablesIcon.value = "copy"), 1000)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const prettifyVariableString = () => {
|
const prettifyVariableString = () => {
|
||||||
@@ -723,7 +725,6 @@ const prettifyVariableString = () => {
|
|||||||
prettifyVariablesIcon.value = "info"
|
prettifyVariablesIcon.value = "info"
|
||||||
toast.error(`${t("error.json_prettify_invalid_body")}`)
|
toast.error(`${t("error.json_prettify_invalid_body")}`)
|
||||||
}
|
}
|
||||||
setTimeout(() => (prettifyVariablesIcon.value = "wand"), 1000)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const clearGQLQuery = () => {
|
const clearGQLQuery = () => {
|
||||||
|
|||||||
@@ -78,6 +78,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive, ref } from "@nuxtjs/composition-api"
|
import { reactive, ref } from "@nuxtjs/composition-api"
|
||||||
|
import { refAutoReset } from "@vueuse/core"
|
||||||
import { useCodemirror } from "~/helpers/editor/codemirror"
|
import { useCodemirror } from "~/helpers/editor/codemirror"
|
||||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
||||||
import {
|
import {
|
||||||
@@ -111,14 +112,16 @@ useCodemirror(
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
const downloadResponseIcon = ref("download")
|
const downloadResponseIcon = refAutoReset<"download" | "check">(
|
||||||
const copyResponseIcon = ref("copy")
|
"download",
|
||||||
|
1000
|
||||||
|
)
|
||||||
|
const copyResponseIcon = refAutoReset<"copy" | "check">("copy", 1000)
|
||||||
|
|
||||||
const copyResponse = () => {
|
const copyResponse = () => {
|
||||||
copyToClipboard(responseString.value!)
|
copyToClipboard(responseString.value!)
|
||||||
copyResponseIcon.value = "check"
|
copyResponseIcon.value = "check"
|
||||||
toast.success(`${t("state.copied_to_clipboard")}`)
|
toast.success(`${t("state.copied_to_clipboard")}`)
|
||||||
setTimeout(() => (copyResponseIcon.value = "copy"), 1000)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const downloadResponse = () => {
|
const downloadResponse = () => {
|
||||||
@@ -135,7 +138,6 @@ const downloadResponse = () => {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
document.body.removeChild(a)
|
document.body.removeChild(a)
|
||||||
URL.revokeObjectURL(url)
|
URL.revokeObjectURL(url)
|
||||||
downloadResponseIcon.value = "download"
|
|
||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
v-model="selectedNavigationTab"
|
v-model="selectedNavigationTab"
|
||||||
styles="sticky bg-primary z-10 top-0"
|
styles="sticky bg-primary z-10 top-0"
|
||||||
vertical
|
vertical
|
||||||
|
render-inactive-tabs
|
||||||
>
|
>
|
||||||
<SmartTab :id="'history'" icon="clock" :label="`${t('tab.history')}`">
|
<SmartTab :id="'history'" icon="clock" :label="`${t('tab.history')}`">
|
||||||
<History
|
<History
|
||||||
@@ -64,6 +65,7 @@
|
|||||||
<SmartTabs
|
<SmartTabs
|
||||||
v-model="selectedGqlTab"
|
v-model="selectedGqlTab"
|
||||||
styles="border-t border-b border-dividerLight bg-primary sticky z-10 top-sidebarPrimaryStickyFold"
|
styles="border-t border-b border-dividerLight bg-primary sticky z-10 top-sidebarPrimaryStickyFold"
|
||||||
|
render-inactive-tabs
|
||||||
>
|
>
|
||||||
<SmartTab
|
<SmartTab
|
||||||
v-if="queryFields.length > 0"
|
v-if="queryFields.length > 0"
|
||||||
@@ -193,6 +195,7 @@ import { computed, nextTick, reactive, ref } from "@nuxtjs/composition-api"
|
|||||||
import { GraphQLField, GraphQLType } from "graphql"
|
import { GraphQLField, GraphQLType } from "graphql"
|
||||||
import { map } from "rxjs/operators"
|
import { map } from "rxjs/operators"
|
||||||
import { GQLHeader } from "@hoppscotch/data"
|
import { GQLHeader } from "@hoppscotch/data"
|
||||||
|
import { refAutoReset } from "@vueuse/core"
|
||||||
import { useCodemirror } from "~/helpers/editor/codemirror"
|
import { useCodemirror } from "~/helpers/editor/codemirror"
|
||||||
import { GQLConnection } from "~/helpers/GQLConnection"
|
import { GQLConnection } from "~/helpers/GQLConnection"
|
||||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
||||||
@@ -306,8 +309,8 @@ const graphqlTypes = useReadonlyStream(
|
|||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
|
|
||||||
const downloadSchemaIcon = ref("download")
|
const downloadSchemaIcon = refAutoReset<"download" | "check">("download", 1000)
|
||||||
const copySchemaIcon = ref("copy")
|
const copySchemaIcon = refAutoReset<"copy" | "check">("copy", 1000)
|
||||||
|
|
||||||
const graphqlFieldsFilterText = ref("")
|
const graphqlFieldsFilterText = ref("")
|
||||||
|
|
||||||
@@ -423,7 +426,6 @@ const downloadSchema = () => {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
document.body.removeChild(a)
|
document.body.removeChild(a)
|
||||||
URL.revokeObjectURL(url)
|
URL.revokeObjectURL(url)
|
||||||
downloadSchemaIcon.value = "download"
|
|
||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -432,7 +434,6 @@ const copySchema = () => {
|
|||||||
|
|
||||||
copyToClipboard(schemaString.value)
|
copyToClipboard(schemaString.value)
|
||||||
copySchemaIcon.value = "check"
|
copySchemaIcon.value = "check"
|
||||||
setTimeout(() => (copySchemaIcon.value = "copy"), 1000)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleUseHistory = (entry: GQLHistoryEntry) => {
|
const handleUseHistory = (entry: GQLHistoryEntry) => {
|
||||||
|
|||||||
@@ -22,7 +22,10 @@
|
|||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<div class="flex flex-col" role="menu">
|
<div
|
||||||
|
class="flex flex-col space-y-1 divide-y divide-dividerLight"
|
||||||
|
role="menu"
|
||||||
|
>
|
||||||
<SmartItem
|
<SmartItem
|
||||||
:label="$t('state.none').toLowerCase()"
|
:label="$t('state.none').toLowerCase()"
|
||||||
:info-icon="contentType === null ? 'done' : ''"
|
:info-icon="contentType === null ? 'done' : ''"
|
||||||
@@ -34,9 +37,24 @@
|
|||||||
}
|
}
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
<div
|
||||||
|
v-for="(
|
||||||
|
contentTypeItems, contentTypeItemsIndex
|
||||||
|
) in segmentedContentTypes"
|
||||||
|
:key="`contentTypeItems-${contentTypeItemsIndex}`"
|
||||||
|
class="flex flex-col py-2 text-left"
|
||||||
|
>
|
||||||
|
<div class="flex rounded py-2 px-4">
|
||||||
|
<span class="text-tiny text-secondaryLight font-bold">
|
||||||
|
{{ $t(contentTypeItems.title) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col">
|
||||||
<SmartItem
|
<SmartItem
|
||||||
v-for="(contentTypeItem, index) in validContentTypes"
|
v-for="(
|
||||||
:key="`contentTypeItem-${index}`"
|
contentTypeItem, contentTypeIndex
|
||||||
|
) in contentTypeItems.contentTypes"
|
||||||
|
:key="`contentTypeItem-${contentTypeIndex}`"
|
||||||
:label="contentTypeItem"
|
:label="contentTypeItem"
|
||||||
:info-icon="contentTypeItem === contentType ? 'done' : ''"
|
:info-icon="contentTypeItem === contentType ? 'done' : ''"
|
||||||
:active-info-icon="contentTypeItem === contentType"
|
:active-info-icon="contentTypeItem === contentType"
|
||||||
@@ -48,6 +66,8 @@
|
|||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</tippy>
|
</tippy>
|
||||||
<ButtonSecondary
|
<ButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
||||||
@@ -106,7 +126,7 @@ import * as A from "fp-ts/Array"
|
|||||||
import * as O from "fp-ts/Option"
|
import * as O from "fp-ts/Option"
|
||||||
import { RequestOptionTabs } from "./RequestOptions.vue"
|
import { RequestOptionTabs } from "./RequestOptions.vue"
|
||||||
import { useStream } from "~/helpers/utils/composables"
|
import { useStream } from "~/helpers/utils/composables"
|
||||||
import { knownContentTypes } from "~/helpers/utils/contenttypes"
|
import { segmentedContentTypes } from "~/helpers/utils/contenttypes"
|
||||||
import {
|
import {
|
||||||
restContentType$,
|
restContentType$,
|
||||||
restHeaders$,
|
restHeaders$,
|
||||||
@@ -119,7 +139,6 @@ const emit = defineEmits<{
|
|||||||
(e: "change-tab", value: string): void
|
(e: "change-tab", value: string): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const validContentTypes = Object.keys(knownContentTypes)
|
|
||||||
const contentType = useStream(restContentType$, null, setRESTContentType)
|
const contentType = useStream(restContentType$, null, setRESTContentType)
|
||||||
|
|
||||||
// The functional headers list (the headers actually in the system)
|
// The functional headers list (the headers actually in the system)
|
||||||
|
|||||||
@@ -38,8 +38,8 @@
|
|||||||
drag-class="cursor-grabbing"
|
drag-class="cursor-grabbing"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-for="(param, index) in workingParams"
|
v-for="({ id, entry }, index) in workingParams"
|
||||||
:key="`param-${index}`"
|
:key="`param=${id}-${index}`"
|
||||||
class="flex border-b divide-x divide-dividerLight border-dividerLight draggable-content group"
|
class="flex border-b divide-x divide-dividerLight border-dividerLight draggable-content group"
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
@@ -54,21 +54,21 @@
|
|||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<SmartEnvInput
|
<SmartEnvInput
|
||||||
v-model="param.key"
|
v-model="entry.key"
|
||||||
:placeholder="`${$t('count.parameter', { count: index + 1 })}`"
|
:placeholder="`${$t('count.parameter', { count: index + 1 })}`"
|
||||||
@change="
|
@change="
|
||||||
updateBodyParam(index, {
|
updateBodyParam(index, {
|
||||||
key: $event,
|
key: $event,
|
||||||
value: param.value,
|
value: entry.value,
|
||||||
active: param.active,
|
active: entry.active,
|
||||||
isFile: param.isFile,
|
isFile: entry.isFile,
|
||||||
})
|
})
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
<div v-if="param.isFile" class="file-chips-container hide-scrollbar">
|
<div v-if="entry.isFile" class="file-chips-container hide-scrollbar">
|
||||||
<div class="space-x-2 file-chips-wrapper">
|
<div class="space-x-2 file-chips-wrapper">
|
||||||
<SmartFileChip
|
<SmartFileChip
|
||||||
v-for="(file, fileIndex) in param.value"
|
v-for="(file, fileIndex) in entry.value"
|
||||||
:key="`param-${index}-file-${fileIndex}`"
|
:key="`param-${index}-file-${fileIndex}`"
|
||||||
>{{ file.name }}</SmartFileChip
|
>{{ file.name }}</SmartFileChip
|
||||||
>
|
>
|
||||||
@@ -76,14 +76,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<span v-else class="flex flex-1">
|
<span v-else class="flex flex-1">
|
||||||
<SmartEnvInput
|
<SmartEnvInput
|
||||||
v-model="param.value"
|
v-model="entry.value"
|
||||||
:placeholder="`${$t('count.value', { count: index + 1 })}`"
|
:placeholder="`${$t('count.value', { count: index + 1 })}`"
|
||||||
@change="
|
@change="
|
||||||
updateBodyParam(index, {
|
updateBodyParam(index, {
|
||||||
key: param.key,
|
key: entry.key,
|
||||||
value: $event,
|
value: $event,
|
||||||
active: param.active,
|
active: entry.active,
|
||||||
isFile: param.isFile,
|
isFile: entry.isFile,
|
||||||
})
|
})
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
@@ -97,7 +97,7 @@
|
|||||||
type="file"
|
type="file"
|
||||||
multiple
|
multiple
|
||||||
class="p-1 cursor-pointer transition file:transition file:cursor-pointer text-secondaryLight hover:text-secondaryDark file:mr-2 file:py-1 file:px-4 file:rounded file:border-0 file:text-tiny text-tiny file:text-secondary hover:file:text-secondaryDark file:bg-primaryLight hover:file:bg-primaryDark"
|
class="p-1 cursor-pointer transition file:transition file:cursor-pointer text-secondaryLight hover:text-secondaryDark file:mr-2 file:py-1 file:px-4 file:rounded file:border-0 file:text-tiny text-tiny file:text-secondary hover:file:text-secondaryDark file:bg-primaryLight hover:file:bg-primaryDark"
|
||||||
@change="setRequestAttachment(index, param, $event)"
|
@change="setRequestAttachment(index, entry, $event)"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
</span>
|
</span>
|
||||||
@@ -105,15 +105,15 @@
|
|||||||
<ButtonSecondary
|
<ButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:title="
|
:title="
|
||||||
param.hasOwnProperty('active')
|
entry.hasOwnProperty('active')
|
||||||
? param.active
|
? entry.active
|
||||||
? $t('action.turn_off')
|
? $t('action.turn_off')
|
||||||
: $t('action.turn_on')
|
: $t('action.turn_on')
|
||||||
: $t('action.turn_off')
|
: $t('action.turn_off')
|
||||||
"
|
"
|
||||||
:svg="
|
:svg="
|
||||||
param.hasOwnProperty('active')
|
entry.hasOwnProperty('active')
|
||||||
? param.active
|
? entry.active
|
||||||
? 'check-circle'
|
? 'check-circle'
|
||||||
: 'circle'
|
: 'circle'
|
||||||
: 'check-circle'
|
: 'check-circle'
|
||||||
@@ -121,10 +121,10 @@
|
|||||||
color="green"
|
color="green"
|
||||||
@click.native="
|
@click.native="
|
||||||
updateBodyParam(index, {
|
updateBodyParam(index, {
|
||||||
key: param.key,
|
key: entry.key,
|
||||||
value: param.value,
|
value: entry.value,
|
||||||
active: param.hasOwnProperty('active') ? !param.active : false,
|
active: entry.hasOwnProperty('active') ? !entry.active : false,
|
||||||
isFile: param.isFile,
|
isFile: entry.isFile,
|
||||||
})
|
})
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
@@ -164,6 +164,9 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, Ref, watch } from "@nuxtjs/composition-api"
|
import { ref, Ref, watch } from "@nuxtjs/composition-api"
|
||||||
|
import { flow, pipe } from "fp-ts/function"
|
||||||
|
import * as O from "fp-ts/Option"
|
||||||
|
import * as A from "fp-ts/Array"
|
||||||
import { FormDataKeyValue } from "@hoppscotch/data"
|
import { FormDataKeyValue } from "@hoppscotch/data"
|
||||||
import isEqual from "lodash/isEqual"
|
import isEqual from "lodash/isEqual"
|
||||||
import { clone } from "lodash"
|
import { clone } from "lodash"
|
||||||
@@ -171,10 +174,14 @@ import draggable from "vuedraggable"
|
|||||||
import { pluckRef, useI18n, useToast } from "~/helpers/utils/composables"
|
import { pluckRef, useI18n, useToast } from "~/helpers/utils/composables"
|
||||||
import { useRESTRequestBody } from "~/newstore/RESTSession"
|
import { useRESTRequestBody } from "~/newstore/RESTSession"
|
||||||
|
|
||||||
|
type WorkingFormDataKeyValue = { id: number; entry: FormDataKeyValue }
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
|
const idTicker = ref(0)
|
||||||
|
|
||||||
const deletionToast = ref<{ goAway: (delay: number) => void } | null>(null)
|
const deletionToast = ref<{ goAway: (delay: number) => void } | null>(null)
|
||||||
|
|
||||||
const bodyParams = pluckRef<any, any>(useRESTRequestBody(), "body") as Ref<
|
const bodyParams = pluckRef<any, any>(useRESTRequestBody(), "body") as Ref<
|
||||||
@@ -182,23 +189,32 @@ const bodyParams = pluckRef<any, any>(useRESTRequestBody(), "body") as Ref<
|
|||||||
>
|
>
|
||||||
|
|
||||||
// The UI representation of the parameters list (has the empty end param)
|
// The UI representation of the parameters list (has the empty end param)
|
||||||
const workingParams = ref<FormDataKeyValue[]>([
|
const workingParams = ref<WorkingFormDataKeyValue[]>([
|
||||||
{
|
{
|
||||||
|
id: idTicker.value++,
|
||||||
|
entry: {
|
||||||
key: "",
|
key: "",
|
||||||
value: "",
|
value: "",
|
||||||
active: true,
|
active: true,
|
||||||
isFile: false,
|
isFile: false,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
// Rule: Working Params always have last element is always an empty param
|
// Rule: Working Params always have last element is always an empty param
|
||||||
watch(workingParams, (paramsList) => {
|
watch(workingParams, (paramsList) => {
|
||||||
if (paramsList.length > 0 && paramsList[paramsList.length - 1].key !== "") {
|
if (
|
||||||
|
paramsList.length > 0 &&
|
||||||
|
paramsList[paramsList.length - 1].entry.key !== ""
|
||||||
|
) {
|
||||||
workingParams.value.push({
|
workingParams.value.push({
|
||||||
|
id: idTicker.value++,
|
||||||
|
entry: {
|
||||||
key: "",
|
key: "",
|
||||||
value: "",
|
value: "",
|
||||||
active: true,
|
active: true,
|
||||||
isFile: false,
|
isFile: false,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -208,19 +224,37 @@ watch(
|
|||||||
bodyParams,
|
bodyParams,
|
||||||
(newParamsList) => {
|
(newParamsList) => {
|
||||||
// Sync should overwrite working params
|
// Sync should overwrite working params
|
||||||
const filteredWorkingParams = workingParams.value.filter(
|
const filteredWorkingParams = pipe(
|
||||||
(e) => e.key !== ""
|
workingParams.value,
|
||||||
|
A.filterMap(
|
||||||
|
flow(
|
||||||
|
O.fromPredicate((e) => e.entry.key !== ""),
|
||||||
|
O.map((e) => e.entry)
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!isEqual(newParamsList, filteredWorkingParams)) {
|
if (!isEqual(newParamsList, filteredWorkingParams)) {
|
||||||
workingParams.value = newParamsList
|
workingParams.value = pipe(
|
||||||
|
newParamsList,
|
||||||
|
A.map((x) => ({ id: idTicker.value++, entry: x }))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
watch(workingParams, (newWorkingParams) => {
|
watch(workingParams, (newWorkingParams) => {
|
||||||
const fixedParams = newWorkingParams.filter((e) => e.key !== "")
|
const fixedParams = pipe(
|
||||||
|
newWorkingParams,
|
||||||
|
A.filterMap(
|
||||||
|
flow(
|
||||||
|
O.fromPredicate((e) => e.entry.key !== ""),
|
||||||
|
O.map((e) => e.entry)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if (!isEqual(bodyParams.value, fixedParams)) {
|
if (!isEqual(bodyParams.value, fixedParams)) {
|
||||||
bodyParams.value = fixedParams
|
bodyParams.value = fixedParams
|
||||||
}
|
}
|
||||||
@@ -228,16 +262,19 @@ watch(workingParams, (newWorkingParams) => {
|
|||||||
|
|
||||||
const addBodyParam = () => {
|
const addBodyParam = () => {
|
||||||
workingParams.value.push({
|
workingParams.value.push({
|
||||||
|
id: idTicker.value++,
|
||||||
|
entry: {
|
||||||
key: "",
|
key: "",
|
||||||
value: "",
|
value: "",
|
||||||
active: true,
|
active: true,
|
||||||
isFile: false,
|
isFile: false,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateBodyParam = (index: number, param: FormDataKeyValue) => {
|
const updateBodyParam = (index: number, entry: FormDataKeyValue) => {
|
||||||
workingParams.value = workingParams.value.map((h, i) =>
|
workingParams.value = workingParams.value.map((h, i) =>
|
||||||
i === index ? param : h
|
i === index ? { id: h.id, entry } : h
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,11 +317,14 @@ const clearContent = () => {
|
|||||||
// set params list to the initial state
|
// set params list to the initial state
|
||||||
workingParams.value = [
|
workingParams.value = [
|
||||||
{
|
{
|
||||||
|
id: idTicker.value++,
|
||||||
|
entry: {
|
||||||
key: "",
|
key: "",
|
||||||
value: "",
|
value: "",
|
||||||
active: true,
|
active: true,
|
||||||
isFile: false,
|
isFile: false,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -87,6 +87,7 @@
|
|||||||
import { computed, ref, watch } from "@nuxtjs/composition-api"
|
import { computed, ref, watch } from "@nuxtjs/composition-api"
|
||||||
import * as O from "fp-ts/Option"
|
import * as O from "fp-ts/Option"
|
||||||
import { Environment, makeRESTRequest } from "@hoppscotch/data"
|
import { Environment, makeRESTRequest } from "@hoppscotch/data"
|
||||||
|
import { refAutoReset } from "@vueuse/core"
|
||||||
import { useCodemirror } from "~/helpers/editor/codemirror"
|
import { useCodemirror } from "~/helpers/editor/codemirror"
|
||||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
||||||
import {
|
import {
|
||||||
@@ -118,9 +119,10 @@ const options = ref<any | null>(null)
|
|||||||
|
|
||||||
const request = ref(getRESTRequest())
|
const request = ref(getRESTRequest())
|
||||||
const codegenType = ref<CodegenName>("shell-curl")
|
const codegenType = ref<CodegenName>("shell-curl")
|
||||||
const copyIcon = ref("copy")
|
|
||||||
const errorState = ref(false)
|
const errorState = ref(false)
|
||||||
|
|
||||||
|
const copyIcon = refAutoReset<"copy" | "check">("copy", 1000)
|
||||||
|
|
||||||
const requestCode = computed(() => {
|
const requestCode = computed(() => {
|
||||||
const aggregateEnvs = getAggregateEnvs()
|
const aggregateEnvs = getAggregateEnvs()
|
||||||
const env: Environment = {
|
const env: Environment = {
|
||||||
@@ -184,7 +186,6 @@ const copyRequestCode = () => {
|
|||||||
copyToClipboard(requestCode.value)
|
copyToClipboard(requestCode.value)
|
||||||
copyIcon.value = "check"
|
copyIcon.value = "check"
|
||||||
toast.success(`${t("state.copied_to_clipboard")}`)
|
toast.success(`${t("state.copied_to_clipboard")}`)
|
||||||
setTimeout(() => (copyIcon.value = "copy"), 1000)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const searchQuery = ref("")
|
const searchQuery = ref("")
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from "@nuxtjs/composition-api"
|
import { ref, watch } from "@nuxtjs/composition-api"
|
||||||
|
import { refAutoReset } from "@vueuse/core"
|
||||||
import { useCodemirror } from "~/helpers/editor/codemirror"
|
import { useCodemirror } from "~/helpers/editor/codemirror"
|
||||||
import { setRESTRequest } from "~/newstore/RESTSession"
|
import { setRESTRequest } from "~/newstore/RESTSession"
|
||||||
import { useI18n, useToast } from "~/helpers/utils/composables"
|
import { useI18n, useToast } from "~/helpers/utils/composables"
|
||||||
@@ -95,7 +96,7 @@ const handleImport = () => {
|
|||||||
hideModal()
|
hideModal()
|
||||||
}
|
}
|
||||||
|
|
||||||
const pasteIcon = ref("clipboard")
|
const pasteIcon = refAutoReset<"clipboard" | "check">("clipboard", 1000)
|
||||||
|
|
||||||
const handlePaste = async () => {
|
const handlePaste = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -103,7 +104,6 @@ const handlePaste = async () => {
|
|||||||
if (text) {
|
if (text) {
|
||||||
curl.value = text
|
curl.value = text
|
||||||
pasteIcon.value = "check"
|
pasteIcon.value = "check"
|
||||||
setTimeout(() => (pasteIcon.value = "clipboard"), 1000)
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to copy: ", e)
|
console.error("Failed to copy: ", e)
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ import { computed, reactive, Ref, ref } from "@nuxtjs/composition-api"
|
|||||||
import * as TO from "fp-ts/TaskOption"
|
import * as TO from "fp-ts/TaskOption"
|
||||||
import { pipe } from "fp-ts/function"
|
import { pipe } from "fp-ts/function"
|
||||||
import { HoppRESTReqBody, ValidContentTypes } from "@hoppscotch/data"
|
import { HoppRESTReqBody, ValidContentTypes } from "@hoppscotch/data"
|
||||||
|
import { refAutoReset } from "@vueuse/core"
|
||||||
import { useCodemirror } from "~/helpers/editor/codemirror"
|
import { useCodemirror } from "~/helpers/editor/codemirror"
|
||||||
import { getEditorLangForMimeType } from "~/helpers/editorutils"
|
import { getEditorLangForMimeType } from "~/helpers/editorutils"
|
||||||
import { pluckRef, useI18n, useToast } from "~/helpers/utils/composables"
|
import { pluckRef, useI18n, useToast } from "~/helpers/utils/composables"
|
||||||
@@ -91,7 +92,8 @@ const rawParamsBody = pluckRef(
|
|||||||
>,
|
>,
|
||||||
"body"
|
"body"
|
||||||
)
|
)
|
||||||
const prettifyIcon = ref("wand")
|
|
||||||
|
const prettifyIcon = refAutoReset<"wand" | "check" | "info">("wand", 1000)
|
||||||
|
|
||||||
const rawInputEditorLang = computed(() =>
|
const rawInputEditorLang = computed(() =>
|
||||||
getEditorLangForMimeType(props.contentType)
|
getEditorLangForMimeType(props.contentType)
|
||||||
@@ -148,6 +150,5 @@ const prettifyRequestBody = () => {
|
|||||||
prettifyIcon.value = "info"
|
prettifyIcon.value = "info"
|
||||||
toast.error(`${t("error.json_prettify_invalid_body")}`)
|
toast.error(`${t("error.json_prettify_invalid_body")}`)
|
||||||
}
|
}
|
||||||
setTimeout(() => (prettifyIcon.value = "wand"), 1000)
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -215,6 +215,7 @@ import { computed, ref, watch } from "@nuxtjs/composition-api"
|
|||||||
import { isLeft, isRight } from "fp-ts/lib/Either"
|
import { isLeft, isRight } from "fp-ts/lib/Either"
|
||||||
import * as E from "fp-ts/Either"
|
import * as E from "fp-ts/Either"
|
||||||
import cloneDeep from "lodash/cloneDeep"
|
import cloneDeep from "lodash/cloneDeep"
|
||||||
|
import { refAutoReset } from "@vueuse/core"
|
||||||
import {
|
import {
|
||||||
updateRESTResponse,
|
updateRESTResponse,
|
||||||
restEndpoint$,
|
restEndpoint$,
|
||||||
@@ -393,7 +394,11 @@ const clearContent = () => {
|
|||||||
resetRESTRequest()
|
resetRESTRequest()
|
||||||
}
|
}
|
||||||
|
|
||||||
const copyLinkIcon = hasNavigatorShare ? ref("share-2") : ref("copy")
|
const copyLinkIcon = refAutoReset<"share-2" | "copy" | "check">(
|
||||||
|
hasNavigatorShare ? "share-2" : "copy",
|
||||||
|
1000
|
||||||
|
)
|
||||||
|
|
||||||
const shareLink = ref<string | null>("")
|
const shareLink = ref<string | null>("")
|
||||||
const fetchingShareLink = ref(false)
|
const fetchingShareLink = ref(false)
|
||||||
|
|
||||||
@@ -448,7 +453,6 @@ const copyShareLink = (shareLink: string) => {
|
|||||||
copyLinkIcon.value = "check"
|
copyLinkIcon.value = "check"
|
||||||
copyToClipboard(`https://hopp.sh/r${shareLink}`)
|
copyToClipboard(`https://hopp.sh/r${shareLink}`)
|
||||||
toast.success(`${t("state.copied_to_clipboard")}`)
|
toast.success(`${t("state.copied_to_clipboard")}`)
|
||||||
setTimeout(() => (copyLinkIcon.value = "copy"), 2000)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
<SmartTabs
|
<SmartTabs
|
||||||
v-model="selectedRealtimeTab"
|
v-model="selectedRealtimeTab"
|
||||||
styles="sticky bg-primary top-upperMobilePrimaryStickyFold sm:top-upperPrimaryStickyFold z-10"
|
styles="sticky bg-primary top-upperMobilePrimaryStickyFold sm:top-upperPrimaryStickyFold z-10"
|
||||||
|
render-inactive-tabs
|
||||||
>
|
>
|
||||||
<SmartTab
|
<SmartTab
|
||||||
:id="'params'"
|
:id="'params'"
|
||||||
|
|||||||
@@ -117,9 +117,21 @@
|
|||||||
<span class="text-secondary"> {{ t("response.time") }}: </span>
|
<span class="text-secondary"> {{ t("response.time") }}: </span>
|
||||||
{{ `${response.meta.responseDuration} ms` }}
|
{{ `${response.meta.responseDuration} ms` }}
|
||||||
</span>
|
</span>
|
||||||
<span v-if="response.meta && response.meta.responseSize">
|
<span
|
||||||
|
v-if="response.meta && response.meta.responseSize"
|
||||||
|
v-tippy="
|
||||||
|
readableResponseSize
|
||||||
|
? { theme: 'tooltip' }
|
||||||
|
: { onShow: () => false }
|
||||||
|
"
|
||||||
|
:title="`${response.meta.responseSize} B`"
|
||||||
|
>
|
||||||
<span class="text-secondary"> {{ t("response.size") }}: </span>
|
<span class="text-secondary"> {{ t("response.size") }}: </span>
|
||||||
{{ `${response.meta.responseSize} B` }}
|
{{
|
||||||
|
readableResponseSize
|
||||||
|
? readableResponseSize
|
||||||
|
: `${response.meta.responseSize} B`
|
||||||
|
}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -141,6 +153,29 @@ const props = defineProps<{
|
|||||||
response: HoppRESTResponse
|
response: HoppRESTResponse
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gives the response size in a human readable format
|
||||||
|
* (changes unit from B to MB/KB depending on the size)
|
||||||
|
* If no changes (error res state) or value can be made (size < 1KB ?),
|
||||||
|
* it returns undefined
|
||||||
|
*/
|
||||||
|
const readableResponseSize = computed(() => {
|
||||||
|
if (
|
||||||
|
props.response.type === "loading" ||
|
||||||
|
props.response.type === "network_fail" ||
|
||||||
|
props.response.type === "script_fail" ||
|
||||||
|
props.response.type === "fail"
|
||||||
|
)
|
||||||
|
return undefined
|
||||||
|
|
||||||
|
const size = props.response.meta.responseSize
|
||||||
|
|
||||||
|
if (size >= 100000) return (size / 1000000).toFixed(2) + " MB"
|
||||||
|
if (size >= 1000) return (size / 1000).toFixed(2) + " KB"
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
})
|
||||||
|
|
||||||
const statusCategory = computed(() => {
|
const statusCategory = computed(() => {
|
||||||
if (
|
if (
|
||||||
props.response.type === "loading" ||
|
props.response.type === "loading" ||
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
v-model="selectedNavigationTab"
|
v-model="selectedNavigationTab"
|
||||||
styles="sticky bg-primary z-10 top-0"
|
styles="sticky bg-primary z-10 top-0"
|
||||||
vertical
|
vertical
|
||||||
|
render-inactive-tabs
|
||||||
>
|
>
|
||||||
<SmartTab :id="'history'" icon="clock" :label="`${$t('tab.history')}`">
|
<SmartTab :id="'history'" icon="clock" :label="`${$t('tab.history')}`">
|
||||||
<History ref="historyComponent" :page="'rest'" />
|
<History ref="historyComponent" :page="'rest'" />
|
||||||
|
|||||||
@@ -12,10 +12,13 @@
|
|||||||
<span class="text-secondaryDark">
|
<span class="text-secondaryDark">
|
||||||
{{ env.key }}
|
{{ env.key }}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-secondaryDark">
|
<span class="text-secondaryDark pl-2 break-all">
|
||||||
{{ ` \xA0 — \xA0 ${env.value}` }}
|
{{ ` \xA0 — \xA0 ${env.value}` }}
|
||||||
</span>
|
</span>
|
||||||
<span v-if="status === 'updations'" class="text-secondaryLight">
|
<span
|
||||||
|
v-if="status === 'updations'"
|
||||||
|
class="text-secondaryLight px-2 break-all"
|
||||||
|
>
|
||||||
{{ ` \xA0 ← \xA0 ${env.previousValue}` }}
|
{{ ` \xA0 ← \xA0 ${env.previousValue}` }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -26,8 +26,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "@nuxtjs/composition-api"
|
|
||||||
import { HoppRESTHeader } from "@hoppscotch/data"
|
import { HoppRESTHeader } from "@hoppscotch/data"
|
||||||
|
import { refAutoReset } from "@vueuse/core"
|
||||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
||||||
import { useI18n, useToast } from "~/helpers/utils/composables"
|
import { useI18n, useToast } from "~/helpers/utils/composables"
|
||||||
|
|
||||||
@@ -39,12 +39,11 @@ const props = defineProps<{
|
|||||||
headers: Array<HoppRESTHeader>
|
headers: Array<HoppRESTHeader>
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const copyIcon = ref("copy")
|
const copyIcon = refAutoReset<"copy" | "check">("copy", 1000)
|
||||||
|
|
||||||
const copyHeaders = () => {
|
const copyHeaders = () => {
|
||||||
copyToClipboard(JSON.stringify(props.headers))
|
copyToClipboard(JSON.stringify(props.headers))
|
||||||
copyIcon.value = "check"
|
copyIcon.value = "check"
|
||||||
toast.success(`${t("state.copied_to_clipboard")}`)
|
toast.success(`${t("state.copied_to_clipboard")}`)
|
||||||
setTimeout(() => (copyIcon.value = "copy"), 1000)
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "@nuxtjs/composition-api"
|
import { refAutoReset } from "@vueuse/core"
|
||||||
import { HoppRESTHeader } from "~/../hoppscotch-data/dist"
|
import { HoppRESTHeader } from "~/../hoppscotch-data/dist"
|
||||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
||||||
import { useI18n, useToast } from "~/helpers/utils/composables"
|
import { useI18n, useToast } from "~/helpers/utils/composables"
|
||||||
@@ -41,12 +41,11 @@ defineProps<{
|
|||||||
header: HoppRESTHeader
|
header: HoppRESTHeader
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const copyIcon = ref("copy")
|
const copyIcon = refAutoReset<"copy" | "check">("copy", 1000)
|
||||||
|
|
||||||
const copyHeader = (headerValue: string) => {
|
const copyHeader = (headerValue: string) => {
|
||||||
copyToClipboard(headerValue)
|
copyToClipboard(headerValue)
|
||||||
copyIcon.value = "check"
|
copyIcon.value = "check"
|
||||||
toast.success(`${t("state.copied_to_clipboard")}`)
|
toast.success(`${t("state.copied_to_clipboard")}`)
|
||||||
setTimeout(() => (copyIcon.value = "copy"), 1000)
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
v-if="response"
|
v-if="response"
|
||||||
v-model="selectedLensTab"
|
v-model="selectedLensTab"
|
||||||
styles="sticky z-10 bg-primary top-lowerPrimaryStickyFold"
|
styles="sticky z-10 bg-primary top-lowerPrimaryStickyFold"
|
||||||
|
render-inactive-tabs
|
||||||
>
|
>
|
||||||
<SmartTab
|
<SmartTab
|
||||||
v-for="(lens, index) in validLenses"
|
v-for="(lens, index) in validLenses"
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col flex-1">
|
<div
|
||||||
|
v-if="response.type === 'success' || response.type === 'fail'"
|
||||||
|
class="flex flex-col flex-1"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
class="sticky z-10 flex items-center justify-between pl-4 border-b bg-primary border-dividerLight top-lowerSecondaryStickyFold"
|
class="sticky z-10 flex items-center justify-between pl-4 border-b bg-primary border-dividerLight top-lowerSecondaryStickyFold"
|
||||||
>
|
>
|
||||||
<label class="font-semibold text-secondaryLight">
|
<label class="font-semibold text-secondaryLight">
|
||||||
{{ t("response.body") }}
|
{{ t("response.body") }}
|
||||||
</label>
|
</label>
|
||||||
<div class="flex">
|
<div class="flex items-center">
|
||||||
<ButtonSecondary
|
<ButtonSecondary
|
||||||
v-if="response.body"
|
v-if="response.body"
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
@@ -15,6 +18,14 @@
|
|||||||
svg="wrap-text"
|
svg="wrap-text"
|
||||||
@click.native.prevent="linewrapEnabled = !linewrapEnabled"
|
@click.native.prevent="linewrapEnabled = !linewrapEnabled"
|
||||||
/>
|
/>
|
||||||
|
<ButtonSecondary
|
||||||
|
v-if="response.body"
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:title="t('action.filter_response')"
|
||||||
|
svg="filter"
|
||||||
|
:class="{ '!text-accent': toggleFilter }"
|
||||||
|
@click.native.prevent="toggleFilterState"
|
||||||
|
/>
|
||||||
<ButtonSecondary
|
<ButtonSecondary
|
||||||
v-if="response.body"
|
v-if="response.body"
|
||||||
ref="downloadResponse"
|
ref="downloadResponse"
|
||||||
@@ -33,7 +44,47 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div ref="jsonResponse" class="flex flex-col flex-1"></div>
|
<div
|
||||||
|
v-if="toggleFilter"
|
||||||
|
class="bg-primary flex sticky top-lowerTertiaryStickyFold z-10 border-b border-dividerLight"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="bg-primaryLight border-divider text-secondaryDark inline-flex flex-1 items-center"
|
||||||
|
>
|
||||||
|
<span class="inline-flex flex-1 items-center px-4">
|
||||||
|
<SmartIcon name="search" class="h-4 w-4 text-secondaryLight" />
|
||||||
|
<input
|
||||||
|
v-model="filterQueryText"
|
||||||
|
v-focus
|
||||||
|
class="input !border-0 !px-2"
|
||||||
|
:placeholder="`${t('response.filter_response_body')}`"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<div
|
||||||
|
v-if="filterResponseError"
|
||||||
|
class="px-2 py-1 text-tiny flex items-center justify-center text-accentContrast rounded"
|
||||||
|
:class="{
|
||||||
|
'bg-red-500':
|
||||||
|
filterResponseError.type === 'JSON_PARSE_FAILED' ||
|
||||||
|
filterResponseError.type === 'JSON_PATH_QUERY_ERROR',
|
||||||
|
'bg-amber-500': filterResponseError.type === 'RESPONSE_EMPTY',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<SmartIcon name="info" class="svg-icons mr-1.5" />
|
||||||
|
<span>{{ filterResponseError.error }}</span>
|
||||||
|
</div>
|
||||||
|
<ButtonSecondary
|
||||||
|
v-if="response.body"
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:title="t('app.wiki')"
|
||||||
|
svg="help-circle"
|
||||||
|
to="https://github.com/JSONPath-Plus/JSONPath"
|
||||||
|
blank
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div ref="jsonResponse" class="flex flex-col flex-1 h-auto h-full"></div>
|
||||||
<div
|
<div
|
||||||
v-if="outlinePath"
|
v-if="outlinePath"
|
||||||
class="sticky bottom-0 z-10 flex px-2 overflow-auto border-t bg-primaryLight border-dividerLight flex-nowrap hide-scrollbar"
|
class="sticky bottom-0 z-10 flex px-2 overflow-auto border-t bg-primaryLight border-dividerLight flex-nowrap hide-scrollbar"
|
||||||
@@ -142,8 +193,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import * as LJSON from "lossless-json"
|
import * as LJSON from "lossless-json"
|
||||||
import * as O from "fp-ts/Option"
|
import * as O from "fp-ts/Option"
|
||||||
|
import * as E from "fp-ts/Either"
|
||||||
import { pipe } from "fp-ts/function"
|
import { pipe } from "fp-ts/function"
|
||||||
import { computed, ref, reactive } from "@nuxtjs/composition-api"
|
import { computed, ref, reactive } from "@nuxtjs/composition-api"
|
||||||
|
import { JSONPath } from "jsonpath-plus"
|
||||||
import { useCodemirror } from "~/helpers/editor/codemirror"
|
import { useCodemirror } from "~/helpers/editor/codemirror"
|
||||||
import { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
|
import { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
|
||||||
import jsonParse, { JSONObjectMember, JSONValue } from "~/helpers/jsonParse"
|
import jsonParse, { JSONObjectMember, JSONValue } from "~/helpers/jsonParse"
|
||||||
@@ -165,16 +218,51 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const { responseBodyText } = useResponseBody(props.response)
|
const { responseBodyText } = useResponseBody(props.response)
|
||||||
|
|
||||||
const { copyIcon, copyResponse } = useCopyResponse(responseBodyText)
|
const toggleFilter = ref(false)
|
||||||
|
const filterQueryText = ref("")
|
||||||
|
|
||||||
const { downloadIcon, downloadResponse } = useDownloadResponse(
|
type BodyParseError =
|
||||||
"application/json",
|
| { type: "JSON_PARSE_FAILED" }
|
||||||
responseBodyText
|
| { type: "JSON_PATH_QUERY_FAILED"; error: Error }
|
||||||
|
|
||||||
|
const responseJsonObject = computed(() =>
|
||||||
|
pipe(
|
||||||
|
responseBodyText.value,
|
||||||
|
E.tryCatchK(
|
||||||
|
LJSON.parse,
|
||||||
|
(): BodyParseError => ({ type: "JSON_PARSE_FAILED" })
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const jsonResponseBodyText = computed(() => {
|
||||||
|
if (filterQueryText.value.length > 0) {
|
||||||
|
return pipe(
|
||||||
|
responseJsonObject.value,
|
||||||
|
E.chain((parsedJSON) =>
|
||||||
|
E.tryCatch(
|
||||||
|
() =>
|
||||||
|
JSONPath({
|
||||||
|
path: filterQueryText.value,
|
||||||
|
json: parsedJSON,
|
||||||
|
}) as undefined,
|
||||||
|
(err): BodyParseError => ({
|
||||||
|
type: "JSON_PATH_QUERY_FAILED",
|
||||||
|
error: err as Error,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
),
|
||||||
|
E.map(JSON.stringify)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return E.right(responseBodyText.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const jsonBodyText = computed(() =>
|
const jsonBodyText = computed(() =>
|
||||||
pipe(
|
pipe(
|
||||||
responseBodyText.value,
|
jsonResponseBodyText.value,
|
||||||
|
E.getOrElse(() => responseBodyText.value),
|
||||||
O.tryCatchK(LJSON.parse),
|
O.tryCatchK(LJSON.parse),
|
||||||
O.map((val) => LJSON.stringify(val, undefined, 2)),
|
O.map((val) => LJSON.stringify(val, undefined, 2)),
|
||||||
O.getOrElse(() => responseBodyText.value)
|
O.getOrElse(() => responseBodyText.value)
|
||||||
@@ -189,6 +277,38 @@ const ast = computed(() =>
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const filterResponseError = computed(() =>
|
||||||
|
pipe(
|
||||||
|
jsonResponseBodyText.value,
|
||||||
|
E.match(
|
||||||
|
(e) => {
|
||||||
|
switch (e.type) {
|
||||||
|
case "JSON_PATH_QUERY_FAILED":
|
||||||
|
return { type: "JSON_PATH_QUERY_ERROR", error: e.error.message }
|
||||||
|
case "JSON_PARSE_FAILED":
|
||||||
|
return {
|
||||||
|
type: "JSON_PARSE_FAILED",
|
||||||
|
error: t("error.json_parsing_failed").toString(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(result) =>
|
||||||
|
result === "[]"
|
||||||
|
? {
|
||||||
|
type: "RESPONSE_EMPTY",
|
||||||
|
error: t("error.no_results_found").toString(),
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
const { copyIcon, copyResponse } = useCopyResponse(jsonBodyText)
|
||||||
|
const { downloadIcon, downloadResponse } = useDownloadResponse(
|
||||||
|
"application/json",
|
||||||
|
jsonBodyText
|
||||||
|
)
|
||||||
|
|
||||||
const outlineOptions = ref<any | null>(null)
|
const outlineOptions = ref<any | null>(null)
|
||||||
const jsonResponse = ref<any | null>(null)
|
const jsonResponse = ref<any | null>(null)
|
||||||
const linewrapEnabled = ref(true)
|
const linewrapEnabled = ref(true)
|
||||||
@@ -227,6 +347,11 @@ const outlinePath = computed(() =>
|
|||||||
O.getOrElseW(() => null)
|
O.getOrElseW(() => null)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const toggleFilterState = () => {
|
||||||
|
filterQueryText.value = ""
|
||||||
|
toggleFilter.value = !toggleFilter.value
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ import { pipe } from "fp-ts/function"
|
|||||||
import * as RR from "fp-ts/ReadonlyRecord"
|
import * as RR from "fp-ts/ReadonlyRecord"
|
||||||
import * as O from "fp-ts/Option"
|
import * as O from "fp-ts/Option"
|
||||||
import { translateToNewRequest } from "@hoppscotch/data"
|
import { translateToNewRequest } from "@hoppscotch/data"
|
||||||
|
import { refAutoReset } from "@vueuse/core"
|
||||||
import { useI18n, useToast } from "~/helpers/utils/composables"
|
import { useI18n, useToast } from "~/helpers/utils/composables"
|
||||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
||||||
import { Shortcode } from "~/helpers/shortcodes/Shortcode"
|
import { Shortcode } from "~/helpers/shortcodes/Shortcode"
|
||||||
@@ -93,7 +94,8 @@ const requestMethodLabels = {
|
|||||||
} as const
|
} as const
|
||||||
|
|
||||||
const timeStampRef = ref()
|
const timeStampRef = ref()
|
||||||
const copyIconRefs = ref<"copy" | "check">("copy")
|
|
||||||
|
const copyIconRefs = refAutoReset<"copy" | "check">("copy", 1000)
|
||||||
|
|
||||||
const parseShortcodeRequest = computed(() =>
|
const parseShortcodeRequest = computed(() =>
|
||||||
pipe(props.shortcode.request, JSON.parse, translateToNewRequest)
|
pipe(props.shortcode.request, JSON.parse, translateToNewRequest)
|
||||||
@@ -118,7 +120,6 @@ const copyShortcode = (codeID: string) => {
|
|||||||
copyToClipboard(`https://hopp.sh/r/${codeID}`)
|
copyToClipboard(`https://hopp.sh/r/${codeID}`)
|
||||||
toast.success(`${t("state.copied_to_clipboard")}`)
|
toast.success(`${t("state.copied_to_clipboard")}`)
|
||||||
copyIconRefs.value = "check"
|
copyIconRefs.value = "check"
|
||||||
setTimeout(() => (copyIconRefs.value = "copy"), 1000)
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="sticky z-10 flex items-center justify-between pl-4 border-b bg-primary border-dividerLight top-upperMobileSecondaryStickyFold sm:top-upperSecondaryStickyFold"
|
class="sticky z-10 flex items-center justify-between pl-4 border-b bg-primary border-dividerLight top-upperSecondaryStickyFold"
|
||||||
>
|
>
|
||||||
<span class="flex items-center">
|
<span class="flex items-center">
|
||||||
<label class="font-semibold text-secondaryLight">
|
<label class="font-semibold text-secondaryLight">
|
||||||
@@ -113,6 +113,7 @@ import { computed, reactive, ref } from "@nuxtjs/composition-api"
|
|||||||
import { pipe } from "fp-ts/function"
|
import { pipe } from "fp-ts/function"
|
||||||
import * as TO from "fp-ts/TaskOption"
|
import * as TO from "fp-ts/TaskOption"
|
||||||
import * as O from "fp-ts/Option"
|
import * as O from "fp-ts/Option"
|
||||||
|
import { refAutoReset } from "@vueuse/core"
|
||||||
import { useCodemirror } from "~/helpers/editor/codemirror"
|
import { useCodemirror } from "~/helpers/editor/codemirror"
|
||||||
import jsonLinter from "~/helpers/editor/linting/json"
|
import jsonLinter from "~/helpers/editor/linting/json"
|
||||||
import { readFileAsText } from "~/helpers/functional/files"
|
import { readFileAsText } from "~/helpers/functional/files"
|
||||||
@@ -145,7 +146,8 @@ const toast = useToast()
|
|||||||
|
|
||||||
const linewrapEnabled = ref(true)
|
const linewrapEnabled = ref(true)
|
||||||
const wsCommunicationBody = ref<HTMLElement>()
|
const wsCommunicationBody = ref<HTMLElement>()
|
||||||
const prettifyIcon = ref<"wand" | "check" | "info">("wand")
|
|
||||||
|
const prettifyIcon = refAutoReset<"wand" | "check" | "info">("wand", 1000)
|
||||||
|
|
||||||
const knownContentTypes = {
|
const knownContentTypes = {
|
||||||
JSON: "application/ld+json",
|
JSON: "application/ld+json",
|
||||||
@@ -216,6 +218,5 @@ const prettifyRequestBody = () => {
|
|||||||
prettifyIcon.value = "info"
|
prettifyIcon.value = "info"
|
||||||
toast.error(`${t("error.json_prettify_invalid_body")}`)
|
toast.error(`${t("error.json_prettify_invalid_body")}`)
|
||||||
}
|
}
|
||||||
setTimeout(() => (prettifyIcon.value = "wand"), 1000)
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -7,12 +7,6 @@
|
|||||||
{{ title }}
|
{{ title }}
|
||||||
</label>
|
</label>
|
||||||
<div>
|
<div>
|
||||||
<ButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="t('action.search')"
|
|
||||||
svg="search"
|
|
||||||
@click.native="toggleSearch = !toggleSearch"
|
|
||||||
/>
|
|
||||||
<ButtonSecondary
|
<ButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:title="t('action.delete')"
|
:title="t('action.delete')"
|
||||||
@@ -43,26 +37,6 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="toggleSearch"
|
|
||||||
class="w-full p-2 sticky top-0 z-10 text-center border-b border-dividerLight"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="bg-primaryLight border-divider text-secondaryDark rounded inline-flex"
|
|
||||||
>
|
|
||||||
<ButtonSecondary svg="search" class="item-center" />
|
|
||||||
|
|
||||||
<input
|
|
||||||
id=""
|
|
||||||
v-model="pattern"
|
|
||||||
type="text"
|
|
||||||
placeholder="Enter search pattern"
|
|
||||||
class="rounded w-64 bg-primaryLight text-secondaryDark text-center"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="log.length !== 0"
|
v-if="log.length !== 0"
|
||||||
ref="logs"
|
ref="logs"
|
||||||
@@ -72,10 +46,9 @@
|
|||||||
class="flex flex-col h-auto h-full border-r divide-y divide-dividerLight border-dividerLight"
|
class="flex flex-col h-auto h-full border-r divide-y divide-dividerLight border-dividerLight"
|
||||||
>
|
>
|
||||||
<RealtimeLogEntry
|
<RealtimeLogEntry
|
||||||
v-for="(entry, index) in logEntries"
|
v-for="(entry, index) in log"
|
||||||
:key="`entry-${index}`"
|
:key="`entry-${index}`"
|
||||||
:entry="entry"
|
:entry="entry"
|
||||||
:highlight-regex="pattern === '' ? undefined : patternRegex"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -83,9 +56,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch } from "@nuxtjs/composition-api"
|
import { ref, PropType, computed, watch } from "@nuxtjs/composition-api"
|
||||||
import { useThrottleFn, useScroll } from "@vueuse/core"
|
import { useThrottleFn, useScroll } from "@vueuse/core"
|
||||||
import { regexEscape } from "~/helpers/functional/regex"
|
|
||||||
import { useI18n } from "~/helpers/utils/composables"
|
import { useI18n } from "~/helpers/utils/composables"
|
||||||
|
|
||||||
export type LogEntryData = {
|
export type LogEntryData = {
|
||||||
@@ -96,7 +68,13 @@ export type LogEntryData = {
|
|||||||
event: "connecting" | "connected" | "disconnected" | "error"
|
event: "connecting" | "connected" | "disconnected" | "error"
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<{ log: LogEntryData[]; title: string }>()
|
const props = defineProps({
|
||||||
|
log: { type: Array as PropType<LogEntryData[]>, default: () => [] },
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "delete"): void
|
(e: "delete"): void
|
||||||
@@ -143,19 +121,6 @@ const toggleAutoscroll = () => {
|
|||||||
autoScrollEnabled.value = !autoScrollEnabled.value
|
autoScrollEnabled.value = !autoScrollEnabled.value
|
||||||
}
|
}
|
||||||
|
|
||||||
const pattern = ref("")
|
|
||||||
const toggleSearch = ref(false)
|
|
||||||
|
|
||||||
const patternRegex = computed(
|
|
||||||
() => new RegExp(regexEscape(pattern.value), "gi")
|
|
||||||
)
|
|
||||||
|
|
||||||
const logEntries = computed(() => {
|
|
||||||
if (patternRegex.value) {
|
|
||||||
return props.log.filter((entry) => entry.payload.match(patternRegex.value))
|
|
||||||
} else return props.log
|
|
||||||
})
|
|
||||||
|
|
||||||
const toggleAutoscrollColor = computed(() =>
|
const toggleAutoscrollColor = computed(() =>
|
||||||
autoScrollEnabled.value ? "text-green-500" : "text-red-500"
|
autoScrollEnabled.value ? "text-green-500" : "text-red-500"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -31,13 +31,7 @@
|
|||||||
<span v-if="entry.prefix !== undefined" class="!inline">{{
|
<span v-if="entry.prefix !== undefined" class="!inline">{{
|
||||||
entry.prefix
|
entry.prefix
|
||||||
}}</span>
|
}}</span>
|
||||||
<span
|
{{ entry.payload }}
|
||||||
v-for="(section, index) in highlightingSections"
|
|
||||||
:key="index"
|
|
||||||
class="!inline"
|
|
||||||
:class="section.mode === 'highlight' ? 'highlight' : ''"
|
|
||||||
>{{ section.text }}</span
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -57,7 +51,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!minimized" class="overflow-hidden bg-primaryLight">
|
<div v-if="!minimized" class="overflow-hidden bg-primaryLight">
|
||||||
<SmartTabs v-model="selectedTab" styles="bg-primaryLight">
|
<SmartTabs
|
||||||
|
v-model="selectedTab"
|
||||||
|
styles="bg-primaryLight"
|
||||||
|
render-inactive-tabs
|
||||||
|
>
|
||||||
<SmartTab v-if="isJSON(entry.payload)" id="json" label="JSON" />
|
<SmartTab v-if="isJSON(entry.payload)" id="json" label="JSON" />
|
||||||
<SmartTab id="raw" label="Raw" />
|
<SmartTab id="raw" label="Raw" />
|
||||||
</SmartTabs>
|
</SmartTabs>
|
||||||
@@ -209,12 +207,11 @@ import * as LJSON from "lossless-json"
|
|||||||
import * as O from "fp-ts/Option"
|
import * as O from "fp-ts/Option"
|
||||||
import { pipe } from "fp-ts/function"
|
import { pipe } from "fp-ts/function"
|
||||||
import { ref, computed, reactive, watch } from "@nuxtjs/composition-api"
|
import { ref, computed, reactive, watch } from "@nuxtjs/composition-api"
|
||||||
import { useTimeAgo } from "@vueuse/core"
|
import { refAutoReset, useTimeAgo } from "@vueuse/core"
|
||||||
import { LogEntryData } from "./Log.vue"
|
import { LogEntryData } from "./Log.vue"
|
||||||
import { useI18n } from "~/helpers/utils/composables"
|
import { useI18n } from "~/helpers/utils/composables"
|
||||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
||||||
import { isJSON } from "~/helpers/functional/json"
|
import { isJSON } from "~/helpers/functional/json"
|
||||||
import { regexFindAllMatches } from "~/helpers/functional/regex"
|
|
||||||
import useCopyResponse from "~/helpers/lenses/composables/useCopyResponse"
|
import useCopyResponse from "~/helpers/lenses/composables/useCopyResponse"
|
||||||
import useDownloadResponse from "~/helpers/lenses/composables/useDownloadResponse"
|
import useDownloadResponse from "~/helpers/lenses/composables/useDownloadResponse"
|
||||||
import { useCodemirror } from "~/helpers/editor/codemirror"
|
import { useCodemirror } from "~/helpers/editor/codemirror"
|
||||||
@@ -227,55 +224,12 @@ import {
|
|||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{ entry: LogEntryData }>()
|
||||||
entry: LogEntryData
|
|
||||||
highlightRegex?: RegExp
|
|
||||||
}>()
|
|
||||||
const outlineOptions = ref<any | null>(null)
|
const outlineOptions = ref<any | null>(null)
|
||||||
const editor = ref<any | null>(null)
|
const editor = ref<any | null>(null)
|
||||||
const linewrapEnabled = ref(true)
|
const linewrapEnabled = ref(true)
|
||||||
const logPayload = computed(() => props.entry.payload)
|
const logPayload = computed(() => props.entry.payload)
|
||||||
|
|
||||||
type HighlightSection = {
|
|
||||||
mode: "normal" | "highlight"
|
|
||||||
text: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const highlightingSections = computed<HighlightSection[]>(() => {
|
|
||||||
if (!props.highlightRegex)
|
|
||||||
return [{ mode: "normal", text: props.entry.payload }]
|
|
||||||
|
|
||||||
const line = props.entry.payload.split("\n")[0]
|
|
||||||
|
|
||||||
const ranges = pipe(line, regexFindAllMatches(props.highlightRegex))
|
|
||||||
|
|
||||||
const result: HighlightSection[] = []
|
|
||||||
let point = 0
|
|
||||||
|
|
||||||
ranges.forEach(({ startIndex, endIndex }) => {
|
|
||||||
if (point < startIndex)
|
|
||||||
result.push({
|
|
||||||
mode: "normal",
|
|
||||||
text: line.slice(point, startIndex),
|
|
||||||
})
|
|
||||||
|
|
||||||
result.push({
|
|
||||||
mode: "highlight",
|
|
||||||
text: line.slice(startIndex, endIndex + 1),
|
|
||||||
})
|
|
||||||
|
|
||||||
point = endIndex + 1
|
|
||||||
})
|
|
||||||
|
|
||||||
if (point < line.length)
|
|
||||||
result.push({
|
|
||||||
mode: "normal",
|
|
||||||
text: line.slice(point, line.length),
|
|
||||||
})
|
|
||||||
|
|
||||||
return result
|
|
||||||
})
|
|
||||||
|
|
||||||
const selectedTab = ref<"json" | "raw">(
|
const selectedTab = ref<"json" | "raw">(
|
||||||
isJSON(props.entry.payload) ? "json" : "raw"
|
isJSON(props.entry.payload) ? "json" : "raw"
|
||||||
)
|
)
|
||||||
@@ -360,11 +314,11 @@ const { downloadIcon, downloadResponse } = useDownloadResponse(
|
|||||||
logPayload
|
logPayload
|
||||||
)
|
)
|
||||||
|
|
||||||
const copyQueryIcon = ref("copy")
|
const copyQueryIcon = refAutoReset<"copy" | "check">("copy", 1000)
|
||||||
|
|
||||||
const copyQuery = (entry: string) => {
|
const copyQuery = (entry: string) => {
|
||||||
copyToClipboard(entry)
|
copyToClipboard(entry)
|
||||||
copyQueryIcon.value = "check"
|
copyQueryIcon.value = "check"
|
||||||
setTimeout(() => (copyQueryIcon.value = "copy"), 1000)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Relative Time
|
// Relative Time
|
||||||
@@ -435,8 +389,4 @@ const iconName = computed(() => ICONS[props.entry.source].iconName)
|
|||||||
.ts-font {
|
.ts-font {
|
||||||
font-size: 0.6rem;
|
font-size: 0.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.highlight {
|
|
||||||
color: yellow;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-show="active" class="flex flex-col flex-1">
|
<div v-if="shouldRender" v-show="active" class="flex flex-col flex-1">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -33,11 +33,24 @@ const tabMeta = computed<TabMeta>(() => ({
|
|||||||
label: props.label,
|
label: props.label,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const { activeTabID, addTabEntry, updateTabEntry, removeTabEntry } =
|
const {
|
||||||
inject<TabProvider>("tabs-system")!
|
activeTabID,
|
||||||
|
renderInactive,
|
||||||
|
addTabEntry,
|
||||||
|
updateTabEntry,
|
||||||
|
removeTabEntry,
|
||||||
|
} = inject<TabProvider>("tabs-system")!
|
||||||
|
|
||||||
const active = computed(() => activeTabID.value === props.id)
|
const active = computed(() => activeTabID.value === props.id)
|
||||||
|
|
||||||
|
const shouldRender = computed(() => {
|
||||||
|
// If render inactive is true, then it should be rendered nonetheless
|
||||||
|
if (renderInactive.value) return true
|
||||||
|
|
||||||
|
// Else, return whatever is the active state
|
||||||
|
return active.value
|
||||||
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
addTabEntry(props.id, tabMeta.value)
|
addTabEntry(props.id, tabMeta.value)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -80,6 +80,8 @@ export type TabMeta = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type TabProvider = {
|
export type TabProvider = {
|
||||||
|
// Whether inactive tabs should remain rendered
|
||||||
|
renderInactive: ComputedRef<boolean>
|
||||||
activeTabID: ComputedRef<string>
|
activeTabID: ComputedRef<string>
|
||||||
addTabEntry: (tabID: string, meta: TabMeta) => void
|
addTabEntry: (tabID: string, meta: TabMeta) => void
|
||||||
updateTabEntry: (tabID: string, newMeta: TabMeta) => void
|
updateTabEntry: (tabID: string, newMeta: TabMeta) => void
|
||||||
@@ -91,6 +93,10 @@ const props = defineProps({
|
|||||||
type: String,
|
type: String,
|
||||||
default: "",
|
default: "",
|
||||||
},
|
},
|
||||||
|
renderInactiveTabs: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
vertical: {
|
vertical: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
@@ -144,6 +150,7 @@ const removeTabEntry = (tabID: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
provide<TabProvider>("tabs-system", {
|
provide<TabProvider>("tabs-system", {
|
||||||
|
renderInactive: computed(() => props.renderInactiveTabs),
|
||||||
activeTabID: computed(() => props.value),
|
activeTabID: computed(() => props.value),
|
||||||
addTabEntry,
|
addTabEntry,
|
||||||
updateTabEntry,
|
updateTabEntry,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import { getRESTRequest, setRESTTestResults } from "~/newstore/RESTSession"
|
|||||||
import {
|
import {
|
||||||
environmentsStore,
|
environmentsStore,
|
||||||
getCurrentEnvironment,
|
getCurrentEnvironment,
|
||||||
getEnviroment,
|
getEnvironment,
|
||||||
getGlobalVariables,
|
getGlobalVariables,
|
||||||
setGlobalEnvVariables,
|
setGlobalEnvVariables,
|
||||||
updateEnvironment,
|
updateEnvironment,
|
||||||
@@ -97,7 +97,7 @@ export const runRESTRequest$ = (): TaskEither<
|
|||||||
setGlobalEnvVariables(runResult.right.envs.global)
|
setGlobalEnvVariables(runResult.right.envs.global)
|
||||||
|
|
||||||
if (environmentsStore.value.currentEnvironmentIndex !== -1) {
|
if (environmentsStore.value.currentEnvironmentIndex !== -1) {
|
||||||
const env = getEnviroment(
|
const env = getEnvironment(
|
||||||
environmentsStore.value.currentEnvironmentIndex
|
environmentsStore.value.currentEnvironmentIndex
|
||||||
)
|
)
|
||||||
updateEnvironment(
|
updateEnvironment(
|
||||||
|
|||||||
@@ -45,28 +45,23 @@ import {
|
|||||||
} from "~/helpers/fb/auth"
|
} from "~/helpers/fb/auth"
|
||||||
|
|
||||||
const BACKEND_GQL_URL =
|
const BACKEND_GQL_URL =
|
||||||
process.env.context === "production"
|
process.env.BACKEND_GQL_URL ?? "https://api.hoppscotch.io/graphql"
|
||||||
? "https://api.hoppscotch.io/graphql"
|
const BACKEND_WS_URL =
|
||||||
: "https://api.hoppscotch.io/graphql"
|
process.env.BACKEND_WS_URL ?? "wss://api.hoppscotch.io/graphql"
|
||||||
|
|
||||||
// const storage = makeDefaultStorage({
|
// const storage = makeDefaultStorage({
|
||||||
// idbName: "hoppcache-v1",
|
// idbName: "hoppcache-v1",
|
||||||
// maxAge: 7,
|
// maxAge: 7,
|
||||||
// })
|
// })
|
||||||
|
|
||||||
const subscriptionClient = new SubscriptionClient(
|
const subscriptionClient = new SubscriptionClient(BACKEND_WS_URL, {
|
||||||
process.env.context === "production"
|
|
||||||
? "wss://api.hoppscotch.io/graphql"
|
|
||||||
: "wss://api.hoppscotch.io/graphql",
|
|
||||||
{
|
|
||||||
reconnect: true,
|
reconnect: true,
|
||||||
connectionParams: () => {
|
connectionParams: () => {
|
||||||
return {
|
return {
|
||||||
authorization: `Bearer ${authIdToken$.value}`,
|
authorization: `Bearer ${authIdToken$.value}`,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
authIdToken$.subscribe(() => {
|
authIdToken$.subscribe(() => {
|
||||||
subscriptionClient.client?.close()
|
subscriptionClient.client?.close()
|
||||||
|
|||||||
@@ -158,14 +158,11 @@ const getXMLBody = (rawData: string) =>
|
|||||||
O.alt(() => O.some(rawData))
|
O.alt(() => O.some(rawData))
|
||||||
)
|
)
|
||||||
|
|
||||||
const getFormattedJSON = (jsonString: string) =>
|
const getFormattedJSON = flow(
|
||||||
pipe(
|
|
||||||
jsonString.replaceAll('\\"', '"'),
|
|
||||||
safeParseJSON,
|
safeParseJSON,
|
||||||
O.map((parsedJSON) => JSON.stringify(parsedJSON, null, 2)),
|
O.map((parsedJSON) => JSON.stringify(parsedJSON, null, 2)),
|
||||||
O.getOrElse(() => "{ }"),
|
O.getOrElse(() => "{ }")
|
||||||
O.of
|
)
|
||||||
)
|
|
||||||
|
|
||||||
const getXWWWFormUrlEncodedBody = flow(
|
const getXWWWFormUrlEncodedBody = flow(
|
||||||
decodeURIComponent,
|
decodeURIComponent,
|
||||||
@@ -191,7 +188,7 @@ export function parseBody(
|
|||||||
case "application/ld+json":
|
case "application/ld+json":
|
||||||
case "application/vnd.api+json":
|
case "application/vnd.api+json":
|
||||||
case "application/json":
|
case "application/json":
|
||||||
return getFormattedJSON(rawData)
|
return O.some(getFormattedJSON(rawData))
|
||||||
|
|
||||||
case "application/x-www-form-urlencoded":
|
case "application/x-www-form-urlencoded":
|
||||||
return getXWWWFormUrlEncodedBody(rawData)
|
return getXWWWFormUrlEncodedBody(rawData)
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ import { Completer } from "./completion"
|
|||||||
import { LinterDefinition } from "./linting/linter"
|
import { LinterDefinition } from "./linting/linter"
|
||||||
import { basicSetup, baseTheme, baseHighlightStyle } from "./themes/baseTheme"
|
import { basicSetup, baseTheme, baseHighlightStyle } from "./themes/baseTheme"
|
||||||
import { HoppEnvironmentPlugin } from "./extensions/HoppEnvironment"
|
import { HoppEnvironmentPlugin } from "./extensions/HoppEnvironment"
|
||||||
import { IndentedLineWrapPlugin } from "./extensions/IndentedLineWrap"
|
|
||||||
// TODO: Migrate from legacy mode
|
// TODO: Migrate from legacy mode
|
||||||
|
|
||||||
type ExtendedEditorConfig = {
|
type ExtendedEditorConfig = {
|
||||||
@@ -238,7 +237,7 @@ export function useCodemirror(
|
|||||||
),
|
),
|
||||||
lineWrapping.of(
|
lineWrapping.of(
|
||||||
options.extendedEditorConfig.lineWrapping
|
options.extendedEditorConfig.lineWrapping
|
||||||
? [IndentedLineWrapPlugin]
|
? [EditorView.lineWrapping]
|
||||||
: []
|
: []
|
||||||
),
|
),
|
||||||
keymap.of([
|
keymap.of([
|
||||||
@@ -325,7 +324,7 @@ export function useCodemirror(
|
|||||||
(newMode) => {
|
(newMode) => {
|
||||||
view.value?.dispatch({
|
view.value?.dispatch({
|
||||||
effects: lineWrapping.reconfigure(
|
effects: lineWrapping.reconfigure(
|
||||||
newMode ? [EditorView.lineWrapping, IndentedLineWrapPlugin] : []
|
newMode ? [EditorView.lineWrapping] : []
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
import { EditorView } from "@codemirror/view"
|
|
||||||
|
|
||||||
const WrappedLineIndenter = EditorView.updateListener.of((update) => {
|
|
||||||
const view = update.view
|
|
||||||
const charWidth = view.defaultCharacterWidth
|
|
||||||
const lineHeight = view.defaultLineHeight
|
|
||||||
const basePadding = 10
|
|
||||||
|
|
||||||
view.viewportLines((line) => {
|
|
||||||
const domAtPos = view.domAtPos(line.from)
|
|
||||||
|
|
||||||
const lineCount = (line.bottom - line.top) / lineHeight
|
|
||||||
|
|
||||||
if (lineCount <= 1) return
|
|
||||||
|
|
||||||
const belowPadding = basePadding * charWidth
|
|
||||||
|
|
||||||
const node = domAtPos.node as HTMLElement
|
|
||||||
node.style.textIndent = `-${belowPadding - charWidth + 1}px`
|
|
||||||
node.style.paddingLeft = `${belowPadding}px`
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
export const IndentedLineWrapPlugin = [
|
|
||||||
EditorView.lineWrapping,
|
|
||||||
WrappedLineIndenter,
|
|
||||||
]
|
|
||||||
@@ -9,6 +9,14 @@ import { flow } from "fp-ts/function"
|
|||||||
export const safeParseJSON = (str: string): O.Option<object> =>
|
export const safeParseJSON = (str: string): O.Option<object> =>
|
||||||
O.tryCatch(() => JSON.parse(str))
|
O.tryCatch(() => JSON.parse(str))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a prettified JSON representation of an object
|
||||||
|
* @param obj The object to get the representation of
|
||||||
|
* @returns The prettified JSON string of the object
|
||||||
|
*/
|
||||||
|
export const prettyPrintJSON = (obj: unknown): O.Option<string> =>
|
||||||
|
O.tryCatch(() => JSON.stringify(obj, null, "\t"))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if given string is a JSON string
|
* Checks if given string is a JSON string
|
||||||
* @param str Raw string to be checked
|
* @param str Raw string to be checked
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
/**
|
|
||||||
* Escapes special regex characters in a string.
|
|
||||||
* @param text The string to transform
|
|
||||||
* @returns Escaped string
|
|
||||||
*/
|
|
||||||
export const regexEscape = (text: string) =>
|
|
||||||
text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&")
|
|
||||||
|
|
||||||
export type RegexMatch = {
|
|
||||||
matchString: string
|
|
||||||
startIndex: number
|
|
||||||
endIndex: number
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all the regex match ranges for a given input
|
|
||||||
* @param regex The Regular Expression to find from
|
|
||||||
* @param input The input string to get match ranges from
|
|
||||||
* @returns An array of `RegexMatch` objects giving info about the matches
|
|
||||||
*/
|
|
||||||
export const regexFindAllMatches = (regex: RegExp) => (input: string) => {
|
|
||||||
const matches: RegexMatch[] = []
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-cond-assign, prettier/prettier
|
|
||||||
for (let match; match = regex.exec(input); match !== null)
|
|
||||||
matches.push({
|
|
||||||
matchString: match[0],
|
|
||||||
startIndex: match.index,
|
|
||||||
endIndex: match.index + match[0].length - 1,
|
|
||||||
})
|
|
||||||
|
|
||||||
return matches
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
import { OpenAPIV2 } from "openapi-types"
|
||||||
|
import * as O from "fp-ts/Option"
|
||||||
|
import { pipe, flow } from "fp-ts/function"
|
||||||
|
import * as A from "fp-ts/Array"
|
||||||
|
import { prettyPrintJSON } from "~/helpers/functional/json"
|
||||||
|
|
||||||
|
type PrimitiveSchemaType = "string" | "integer" | "number" | "boolean"
|
||||||
|
|
||||||
|
type SchemaType = "array" | "object" | PrimitiveSchemaType
|
||||||
|
|
||||||
|
type PrimitiveRequestBodyExample = number | string | boolean
|
||||||
|
|
||||||
|
type RequestBodyExample =
|
||||||
|
| { [name: string]: RequestBodyExample }
|
||||||
|
| Array<RequestBodyExample>
|
||||||
|
| PrimitiveRequestBodyExample
|
||||||
|
|
||||||
|
const getPrimitiveTypePlaceholder = (
|
||||||
|
schemaType: PrimitiveSchemaType
|
||||||
|
): PrimitiveRequestBodyExample => {
|
||||||
|
switch (schemaType) {
|
||||||
|
case "string":
|
||||||
|
return "string"
|
||||||
|
case "integer":
|
||||||
|
case "number":
|
||||||
|
return 1
|
||||||
|
case "boolean":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSchemaTypeFromSchemaObject = (
|
||||||
|
schema: OpenAPIV2.SchemaObject
|
||||||
|
): O.Option<SchemaType> =>
|
||||||
|
pipe(
|
||||||
|
schema.type,
|
||||||
|
O.fromNullable,
|
||||||
|
O.map(
|
||||||
|
(schemaType) =>
|
||||||
|
(Array.isArray(schemaType) ? schemaType[0] : schemaType) as SchemaType
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
const isSchemaTypePrimitive = (
|
||||||
|
schemaType: string
|
||||||
|
): schemaType is PrimitiveSchemaType =>
|
||||||
|
["string", "integer", "number", "boolean"].includes(schemaType)
|
||||||
|
|
||||||
|
const isSchemaTypeArray = (schemaType: string): schemaType is "array" =>
|
||||||
|
schemaType === "array"
|
||||||
|
|
||||||
|
const isSchemaTypeObject = (schemaType: string): schemaType is "object" =>
|
||||||
|
schemaType === "object"
|
||||||
|
|
||||||
|
const getSampleEnumValueOrPlaceholder = (
|
||||||
|
schema: OpenAPIV2.SchemaObject
|
||||||
|
): RequestBodyExample =>
|
||||||
|
pipe(
|
||||||
|
schema.enum,
|
||||||
|
O.fromNullable,
|
||||||
|
O.map((enums) => enums[0] as RequestBodyExample),
|
||||||
|
O.altW(() =>
|
||||||
|
pipe(
|
||||||
|
schema,
|
||||||
|
getSchemaTypeFromSchemaObject,
|
||||||
|
O.filter(isSchemaTypePrimitive),
|
||||||
|
O.map(getPrimitiveTypePlaceholder)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
O.getOrElseW(() => "")
|
||||||
|
)
|
||||||
|
|
||||||
|
const generateExampleArrayFromOpenAPIV2ItemsObject = (
|
||||||
|
items: OpenAPIV2.ItemsObject
|
||||||
|
): RequestBodyExample =>
|
||||||
|
// ItemsObject can not hold type "object"
|
||||||
|
// https://swagger.io/specification/v2/#itemsObject
|
||||||
|
|
||||||
|
// TODO : Handle array of objects
|
||||||
|
// https://stackoverflow.com/questions/60490974/how-to-define-an-array-of-objects-in-openapi-2-0
|
||||||
|
|
||||||
|
pipe(
|
||||||
|
items,
|
||||||
|
O.fromPredicate(
|
||||||
|
flow((items) => items.type as SchemaType, isSchemaTypePrimitive)
|
||||||
|
),
|
||||||
|
O.map(
|
||||||
|
flow(getSampleEnumValueOrPlaceholder, (arrayItem) => [
|
||||||
|
arrayItem,
|
||||||
|
arrayItem,
|
||||||
|
])
|
||||||
|
),
|
||||||
|
O.getOrElse(() =>
|
||||||
|
// If the type is not primitive, it is "array"
|
||||||
|
// items property is required if type is array
|
||||||
|
[
|
||||||
|
generateExampleArrayFromOpenAPIV2ItemsObject(
|
||||||
|
items.items as OpenAPIV2.ItemsObject
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
const generateRequestBodyExampleFromOpenAPIV2BodySchema = (
|
||||||
|
schema: OpenAPIV2.SchemaObject
|
||||||
|
): RequestBodyExample => {
|
||||||
|
if (schema.example) return schema.example as RequestBodyExample
|
||||||
|
|
||||||
|
const primitiveTypeExample = pipe(
|
||||||
|
schema,
|
||||||
|
O.fromPredicate(
|
||||||
|
flow(
|
||||||
|
getSchemaTypeFromSchemaObject,
|
||||||
|
O.map(isSchemaTypePrimitive),
|
||||||
|
O.getOrElseW(() => false) // No schema type found in the schema object, assume non-primitive
|
||||||
|
)
|
||||||
|
),
|
||||||
|
O.map(getSampleEnumValueOrPlaceholder) // Use enum or placeholder to populate primitive field
|
||||||
|
)
|
||||||
|
|
||||||
|
if (O.isSome(primitiveTypeExample)) return primitiveTypeExample.value
|
||||||
|
|
||||||
|
const arrayTypeExample = pipe(
|
||||||
|
schema,
|
||||||
|
O.fromPredicate(
|
||||||
|
flow(
|
||||||
|
getSchemaTypeFromSchemaObject,
|
||||||
|
O.map(isSchemaTypeArray),
|
||||||
|
O.getOrElseW(() => false) // No schema type found in the schema object, assume type to be different from array
|
||||||
|
)
|
||||||
|
),
|
||||||
|
O.map((schema) => schema.items as OpenAPIV2.ItemsObject),
|
||||||
|
O.map(generateExampleArrayFromOpenAPIV2ItemsObject)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (O.isSome(arrayTypeExample)) return arrayTypeExample.value
|
||||||
|
|
||||||
|
return pipe(
|
||||||
|
schema,
|
||||||
|
O.fromPredicate(
|
||||||
|
flow(
|
||||||
|
getSchemaTypeFromSchemaObject,
|
||||||
|
O.map(isSchemaTypeObject),
|
||||||
|
O.getOrElseW(() => false)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
O.chain((schema) =>
|
||||||
|
pipe(
|
||||||
|
schema.properties,
|
||||||
|
O.fromNullable,
|
||||||
|
O.map(
|
||||||
|
(properties) =>
|
||||||
|
Object.entries(properties) as [string, OpenAPIV2.SchemaObject][]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
O.getOrElseW(() => [] as [string, OpenAPIV2.SchemaObject][]),
|
||||||
|
A.reduce(
|
||||||
|
{} as { [name: string]: RequestBodyExample },
|
||||||
|
(aggregatedExample, property) => {
|
||||||
|
const example = generateRequestBodyExampleFromOpenAPIV2BodySchema(
|
||||||
|
property[1]
|
||||||
|
)
|
||||||
|
aggregatedExample[property[0]] = example
|
||||||
|
return aggregatedExample
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const generateRequestBodyExampleFromOpenAPIV2Body = (
|
||||||
|
op: OpenAPIV2.OperationObject
|
||||||
|
): string =>
|
||||||
|
pipe(
|
||||||
|
(op.parameters ?? []) as OpenAPIV2.Parameter[],
|
||||||
|
A.findFirst((param) => param.in === "body"),
|
||||||
|
O.map(
|
||||||
|
flow(
|
||||||
|
(parameter) => parameter.schema,
|
||||||
|
generateRequestBodyExampleFromOpenAPIV2BodySchema
|
||||||
|
)
|
||||||
|
),
|
||||||
|
O.chain(prettyPrintJSON),
|
||||||
|
O.getOrElse(() => "")
|
||||||
|
)
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
import { OpenAPIV3 } from "openapi-types"
|
||||||
|
import { pipe } from "fp-ts/function"
|
||||||
|
import * as O from "fp-ts/Option"
|
||||||
|
import { tupleToRecord } from "~/helpers/functional/record"
|
||||||
|
|
||||||
|
type SchemaType =
|
||||||
|
| OpenAPIV3.ArraySchemaObjectType
|
||||||
|
| OpenAPIV3.NonArraySchemaObjectType
|
||||||
|
|
||||||
|
type PrimitiveSchemaType = Exclude<SchemaType, "array" | "object">
|
||||||
|
|
||||||
|
type PrimitiveRequestBodyExample = string | number | boolean | null
|
||||||
|
|
||||||
|
type RequestBodyExample =
|
||||||
|
| PrimitiveRequestBodyExample
|
||||||
|
| Array<RequestBodyExample>
|
||||||
|
| { [name: string]: RequestBodyExample }
|
||||||
|
|
||||||
|
const isSchemaTypePrimitive = (
|
||||||
|
schemaType: SchemaType
|
||||||
|
): schemaType is PrimitiveSchemaType =>
|
||||||
|
!["array", "object"].includes(schemaType)
|
||||||
|
|
||||||
|
const getPrimitiveTypePlaceholder = (
|
||||||
|
primitiveType: PrimitiveSchemaType
|
||||||
|
): PrimitiveRequestBodyExample => {
|
||||||
|
switch (primitiveType) {
|
||||||
|
case "number":
|
||||||
|
return 0.0
|
||||||
|
case "integer":
|
||||||
|
return 0
|
||||||
|
case "string":
|
||||||
|
return "string"
|
||||||
|
case "boolean":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use carefully, call only when type is primitive
|
||||||
|
// TODO(agarwal): Use Enum values, if any
|
||||||
|
const generatePrimitiveRequestBodyExample = (
|
||||||
|
schemaObject: OpenAPIV3.NonArraySchemaObject
|
||||||
|
): RequestBodyExample =>
|
||||||
|
getPrimitiveTypePlaceholder(schemaObject.type as PrimitiveSchemaType)
|
||||||
|
|
||||||
|
// Use carefully, call only when type is object
|
||||||
|
const generateObjectRequestBodyExample = (
|
||||||
|
schemaObject: OpenAPIV3.NonArraySchemaObject
|
||||||
|
): RequestBodyExample =>
|
||||||
|
pipe(
|
||||||
|
schemaObject.properties,
|
||||||
|
O.fromNullable,
|
||||||
|
O.map(Object.entries),
|
||||||
|
O.getOrElseW(() => [] as [string, OpenAPIV3.SchemaObject][]),
|
||||||
|
tupleToRecord
|
||||||
|
)
|
||||||
|
|
||||||
|
const generateArrayRequestBodyExample = (
|
||||||
|
schemaObject: OpenAPIV3.ArraySchemaObject
|
||||||
|
): RequestBodyExample => [
|
||||||
|
generateRequestBodyExampleFromSchemaObject(
|
||||||
|
schemaObject.items as OpenAPIV3.SchemaObject
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
const generateRequestBodyExampleFromSchemaObject = (
|
||||||
|
schemaObject: OpenAPIV3.SchemaObject
|
||||||
|
): RequestBodyExample => {
|
||||||
|
// TODO: Handle schema objects with allof
|
||||||
|
if (schemaObject.example) return schemaObject.example as RequestBodyExample
|
||||||
|
|
||||||
|
// If request body can be oneof or allof several schema, choose the first schema to generate an example
|
||||||
|
if (schemaObject.oneOf)
|
||||||
|
return generateRequestBodyExampleFromSchemaObject(
|
||||||
|
schemaObject.oneOf[0] as OpenAPIV3.SchemaObject
|
||||||
|
)
|
||||||
|
if (schemaObject.anyOf)
|
||||||
|
return generateRequestBodyExampleFromSchemaObject(
|
||||||
|
schemaObject.anyOf[0] as OpenAPIV3.SchemaObject
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!schemaObject.type) return ""
|
||||||
|
|
||||||
|
if (isSchemaTypePrimitive(schemaObject.type))
|
||||||
|
return generatePrimitiveRequestBodyExample(
|
||||||
|
schemaObject as OpenAPIV3.NonArraySchemaObject
|
||||||
|
)
|
||||||
|
|
||||||
|
if (schemaObject.type === "object")
|
||||||
|
return generateObjectRequestBodyExample(
|
||||||
|
schemaObject as OpenAPIV3.NonArraySchemaObject
|
||||||
|
)
|
||||||
|
|
||||||
|
return generateArrayRequestBodyExample(
|
||||||
|
schemaObject as OpenAPIV3.ArraySchemaObject
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const generateRequestBodyExampleFromMediaObject = (
|
||||||
|
mediaObject: OpenAPIV3.MediaTypeObject
|
||||||
|
): RequestBodyExample => {
|
||||||
|
if (mediaObject.example) return mediaObject.example as RequestBodyExample
|
||||||
|
if (mediaObject.examples) return mediaObject.examples[0] as RequestBodyExample
|
||||||
|
return mediaObject.schema
|
||||||
|
? generateRequestBodyExampleFromSchemaObject(
|
||||||
|
mediaObject.schema as OpenAPIV3.SchemaObject
|
||||||
|
)
|
||||||
|
: ""
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
import { OpenAPIV3_1 as OpenAPIV31 } from "openapi-types"
|
||||||
|
import { pipe } from "fp-ts/function"
|
||||||
|
import * as O from "fp-ts/Option"
|
||||||
|
import * as A from "fp-ts/Array"
|
||||||
|
|
||||||
|
type MixedArraySchemaType = (
|
||||||
|
| OpenAPIV31.ArraySchemaObjectType
|
||||||
|
| OpenAPIV31.NonArraySchemaObjectType
|
||||||
|
)[]
|
||||||
|
|
||||||
|
type SchemaType =
|
||||||
|
| OpenAPIV31.ArraySchemaObjectType
|
||||||
|
| OpenAPIV31.NonArraySchemaObjectType
|
||||||
|
| MixedArraySchemaType
|
||||||
|
|
||||||
|
type PrimitiveSchemaType = Exclude<
|
||||||
|
OpenAPIV31.NonArraySchemaObjectType,
|
||||||
|
"object"
|
||||||
|
>
|
||||||
|
|
||||||
|
type PrimitiveRequestBodyExample = string | number | boolean | null
|
||||||
|
|
||||||
|
type RequestBodyExample =
|
||||||
|
| PrimitiveRequestBodyExample
|
||||||
|
| Array<RequestBodyExample>
|
||||||
|
| { [name: string]: RequestBodyExample }
|
||||||
|
|
||||||
|
const isSchemaTypePrimitive = (
|
||||||
|
schemaType: SchemaType
|
||||||
|
): schemaType is PrimitiveSchemaType =>
|
||||||
|
!Array.isArray(schemaType) && !["array", "object"].includes(schemaType)
|
||||||
|
|
||||||
|
const getPrimitiveTypePlaceholder = (
|
||||||
|
primitiveType: PrimitiveSchemaType
|
||||||
|
): PrimitiveRequestBodyExample => {
|
||||||
|
switch (primitiveType) {
|
||||||
|
case "number":
|
||||||
|
return 0.0
|
||||||
|
case "integer":
|
||||||
|
return 0
|
||||||
|
case "string":
|
||||||
|
return "string"
|
||||||
|
case "boolean":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use carefully, the schema type should necessarily be primitive
|
||||||
|
// TODO(agarwal): Use Enum values, if any
|
||||||
|
const generatePrimitiveRequestBodyExample = (
|
||||||
|
schemaObject: OpenAPIV31.NonArraySchemaObject
|
||||||
|
): RequestBodyExample =>
|
||||||
|
getPrimitiveTypePlaceholder(schemaObject.type as PrimitiveSchemaType)
|
||||||
|
|
||||||
|
// Use carefully, the schema type should necessarily be object
|
||||||
|
const generateObjectRequestBodyExample = (
|
||||||
|
schemaObject: OpenAPIV31.NonArraySchemaObject
|
||||||
|
): RequestBodyExample =>
|
||||||
|
pipe(
|
||||||
|
schemaObject.properties,
|
||||||
|
O.fromNullable,
|
||||||
|
O.map(
|
||||||
|
(properties) =>
|
||||||
|
Object.entries(properties) as [string, OpenAPIV31.SchemaObject][]
|
||||||
|
),
|
||||||
|
O.getOrElseW(() => [] as [string, OpenAPIV31.SchemaObject][]),
|
||||||
|
A.reduce(
|
||||||
|
{} as { [name: string]: RequestBodyExample },
|
||||||
|
(aggregatedExample, property) => {
|
||||||
|
aggregatedExample[property[0]] =
|
||||||
|
generateRequestBodyExampleFromSchemaObject(property[1])
|
||||||
|
return aggregatedExample
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Use carefully, the schema type should necessarily be mixed array
|
||||||
|
const generateMixedArrayRequestBodyEcample = (
|
||||||
|
schemaObject: OpenAPIV31.SchemaObject
|
||||||
|
): RequestBodyExample =>
|
||||||
|
pipe(
|
||||||
|
schemaObject,
|
||||||
|
(schemaObject) => schemaObject.type as MixedArraySchemaType,
|
||||||
|
A.reduce([] as Array<RequestBodyExample>, (aggregatedExample, itemType) => {
|
||||||
|
// TODO: Figure out how to include non-primitive types as well
|
||||||
|
if (isSchemaTypePrimitive(itemType)) {
|
||||||
|
aggregatedExample.push(getPrimitiveTypePlaceholder(itemType))
|
||||||
|
}
|
||||||
|
return aggregatedExample
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const generateArrayRequestBodyExample = (
|
||||||
|
schemaObject: OpenAPIV31.ArraySchemaObject
|
||||||
|
): RequestBodyExample => [
|
||||||
|
generateRequestBodyExampleFromSchemaObject(
|
||||||
|
schemaObject.items as OpenAPIV31.SchemaObject
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
const generateRequestBodyExampleFromSchemaObject = (
|
||||||
|
schemaObject: OpenAPIV31.SchemaObject
|
||||||
|
): RequestBodyExample => {
|
||||||
|
// TODO: Handle schema objects with oneof or anyof
|
||||||
|
if (schemaObject.example) return schemaObject.example as RequestBodyExample
|
||||||
|
if (schemaObject.examples)
|
||||||
|
return schemaObject.examples[0] as RequestBodyExample
|
||||||
|
if (!schemaObject.type) return ""
|
||||||
|
if (isSchemaTypePrimitive(schemaObject.type))
|
||||||
|
return generatePrimitiveRequestBodyExample(
|
||||||
|
schemaObject as OpenAPIV31.NonArraySchemaObject
|
||||||
|
)
|
||||||
|
if (schemaObject.type === "object")
|
||||||
|
return generateObjectRequestBodyExample(schemaObject)
|
||||||
|
if (schemaObject.type === "array")
|
||||||
|
return generateArrayRequestBodyExample(schemaObject)
|
||||||
|
return generateMixedArrayRequestBodyEcample(schemaObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const generateRequestBodyExampleFromMediaObject = (
|
||||||
|
mediaObject: OpenAPIV31.MediaTypeObject
|
||||||
|
): RequestBodyExample => {
|
||||||
|
if (mediaObject.example) return mediaObject.example as RequestBodyExample
|
||||||
|
if (mediaObject.examples) return mediaObject.examples[0] as RequestBodyExample
|
||||||
|
return mediaObject.schema
|
||||||
|
? generateRequestBodyExampleFromSchemaObject(mediaObject.schema)
|
||||||
|
: ""
|
||||||
|
}
|
||||||
@@ -24,8 +24,12 @@ import * as S from "fp-ts/string"
|
|||||||
import * as O from "fp-ts/Option"
|
import * as O from "fp-ts/Option"
|
||||||
import * as TE from "fp-ts/TaskEither"
|
import * as TE from "fp-ts/TaskEither"
|
||||||
import * as RA from "fp-ts/ReadonlyArray"
|
import * as RA from "fp-ts/ReadonlyArray"
|
||||||
import { step } from "../steps"
|
import { step } from "../../steps"
|
||||||
import { defineImporter, IMPORTER_INVALID_FILE_FORMAT } from "."
|
import { defineImporter, IMPORTER_INVALID_FILE_FORMAT } from "../"
|
||||||
|
import { generateRequestBodyExampleFromMediaObject as generateExampleV31 } from "./exampleV31"
|
||||||
|
import { generateRequestBodyExampleFromMediaObject as generateExampleV3 } from "./exampleV3"
|
||||||
|
import { generateRequestBodyExampleFromOpenAPIV2Body } from "./exampleV2"
|
||||||
|
import { prettyPrintJSON } from "~/helpers/functional/json"
|
||||||
|
|
||||||
export const OPENAPI_DEREF_ERROR = "openapi/deref_error" as const
|
export const OPENAPI_DEREF_ERROR = "openapi/deref_error" as const
|
||||||
|
|
||||||
@@ -114,8 +118,12 @@ const parseOpenAPIV2Body = (op: OpenAPIV2.OperationObject): HoppRESTReqBody => {
|
|||||||
if (
|
if (
|
||||||
obj !== "multipart/form-data" &&
|
obj !== "multipart/form-data" &&
|
||||||
obj !== "application/x-www-form-urlencoded"
|
obj !== "application/x-www-form-urlencoded"
|
||||||
)
|
) {
|
||||||
return { contentType: obj as any, body: "" }
|
return {
|
||||||
|
contentType: obj as any,
|
||||||
|
body: generateRequestBodyExampleFromOpenAPIV2Body(op),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const formDataValues = pipe(
|
const formDataValues = pipe(
|
||||||
(op.parameters ?? []) as OpenAPIV2.Parameter[],
|
(op.parameters ?? []) as OpenAPIV2.Parameter[],
|
||||||
@@ -178,7 +186,8 @@ const parseOpenAPIV3BodyFormData = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const parseOpenAPIV3Body = (
|
const parseOpenAPIV3Body = (
|
||||||
op: OpenAPIV3.OperationObject | OpenAPIV31.OperationObject
|
op: OpenAPIV3.OperationObject | OpenAPIV31.OperationObject,
|
||||||
|
isV31Request: boolean
|
||||||
): HoppRESTReqBody => {
|
): HoppRESTReqBody => {
|
||||||
const objs = Object.entries(
|
const objs = Object.entries(
|
||||||
(
|
(
|
||||||
@@ -197,11 +206,20 @@ const parseOpenAPIV3Body = (
|
|||||||
OpenAPIV3.MediaTypeObject | OpenAPIV31.MediaTypeObject
|
OpenAPIV3.MediaTypeObject | OpenAPIV31.MediaTypeObject
|
||||||
] = objs[0]
|
] = objs[0]
|
||||||
|
|
||||||
|
const exampleBody = pipe(
|
||||||
|
prettyPrintJSON(
|
||||||
|
isV31Request
|
||||||
|
? generateExampleV31(media as OpenAPIV31.MediaTypeObject)
|
||||||
|
: generateExampleV3(media as OpenAPIV3.MediaTypeObject)
|
||||||
|
),
|
||||||
|
O.getOrElse(() => "")
|
||||||
|
)
|
||||||
|
|
||||||
return contentType in knownContentTypes
|
return contentType in knownContentTypes
|
||||||
? contentType === "multipart/form-data" ||
|
? contentType === "multipart/form-data" ||
|
||||||
contentType === "application/x-www-form-urlencoded"
|
contentType === "application/x-www-form-urlencoded"
|
||||||
? parseOpenAPIV3BodyFormData(contentType, media)
|
? parseOpenAPIV3BodyFormData(contentType, media)
|
||||||
: { contentType: contentType as any, body: "" }
|
: { contentType: contentType as any, body: exampleBody }
|
||||||
: { contentType: null, body: null }
|
: { contentType: null, body: null }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,12 +231,20 @@ const isOpenAPIV3Operation = (
|
|||||||
typeof doc.openapi === "string" &&
|
typeof doc.openapi === "string" &&
|
||||||
doc.openapi.startsWith("3.")
|
doc.openapi.startsWith("3.")
|
||||||
|
|
||||||
|
const isOpenAPIV31Operation = (
|
||||||
|
doc: OpenAPI.Document,
|
||||||
|
op: OpenAPIOperationType
|
||||||
|
): op is OpenAPIV31.OperationObject =>
|
||||||
|
objectHasProperty(doc, "openapi") &&
|
||||||
|
typeof doc.openapi === "string" &&
|
||||||
|
doc.openapi.startsWith("3.1")
|
||||||
|
|
||||||
const parseOpenAPIBody = (
|
const parseOpenAPIBody = (
|
||||||
doc: OpenAPI.Document,
|
doc: OpenAPI.Document,
|
||||||
op: OpenAPIOperationType
|
op: OpenAPIOperationType
|
||||||
): HoppRESTReqBody =>
|
): HoppRESTReqBody =>
|
||||||
isOpenAPIV3Operation(doc, op)
|
isOpenAPIV3Operation(doc, op)
|
||||||
? parseOpenAPIV3Body(op)
|
? parseOpenAPIV3Body(op, isOpenAPIV31Operation(doc, op))
|
||||||
: parseOpenAPIV2Body(op)
|
: parseOpenAPIV2Body(op)
|
||||||
|
|
||||||
const resolveOpenAPIV3SecurityObj = (
|
const resolveOpenAPIV3SecurityObj = (
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Ref, ref } from "@nuxtjs/composition-api"
|
import { Ref } from "@nuxtjs/composition-api"
|
||||||
|
import { refAutoReset } from "@vueuse/core"
|
||||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
||||||
import { useI18n, useToast } from "~/helpers/utils/composables"
|
import { useI18n, useToast } from "~/helpers/utils/composables"
|
||||||
|
|
||||||
@@ -8,13 +9,13 @@ export default function useCopyResponse(responseBodyText: Ref<any>): {
|
|||||||
} {
|
} {
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const copyIcon = ref("copy")
|
|
||||||
|
const copyIcon = refAutoReset<"copy" | "check">("copy", 1000)
|
||||||
|
|
||||||
const copyResponse = () => {
|
const copyResponse = () => {
|
||||||
copyToClipboard(responseBodyText.value)
|
copyToClipboard(responseBodyText.value)
|
||||||
copyIcon.value = "check"
|
copyIcon.value = "check"
|
||||||
toast.success(`${t("state.copied_to_clipboard")}`)
|
toast.success(`${t("state.copied_to_clipboard")}`)
|
||||||
setTimeout(() => (copyIcon.value = "copy"), 1000)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import * as S from "fp-ts/string"
|
import * as S from "fp-ts/string"
|
||||||
import * as RNEA from "fp-ts/ReadonlyNonEmptyArray"
|
import * as RNEA from "fp-ts/ReadonlyNonEmptyArray"
|
||||||
import { pipe } from "fp-ts/function"
|
import { pipe } from "fp-ts/function"
|
||||||
import { Ref, ref } from "@nuxtjs/composition-api"
|
import { Ref } from "@nuxtjs/composition-api"
|
||||||
|
import { refAutoReset } from "@vueuse/core"
|
||||||
import { useI18n, useToast } from "~/helpers/utils/composables"
|
import { useI18n, useToast } from "~/helpers/utils/composables"
|
||||||
|
|
||||||
export type downloadResponseReturnType = (() => void) | Ref<any>
|
export type downloadResponseReturnType = (() => void) | Ref<any>
|
||||||
@@ -13,7 +14,8 @@ export default function useDownloadResponse(
|
|||||||
downloadIcon: Ref<string>
|
downloadIcon: Ref<string>
|
||||||
downloadResponse: () => void
|
downloadResponse: () => void
|
||||||
} {
|
} {
|
||||||
const downloadIcon = ref("download")
|
const downloadIcon = refAutoReset<"download" | "check">("download", 1000)
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
@@ -42,7 +44,6 @@ export default function useDownloadResponse(
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
document.body.removeChild(a)
|
document.body.removeChild(a)
|
||||||
URL.revokeObjectURL(url)
|
URL.revokeObjectURL(url)
|
||||||
downloadIcon.value = "download"
|
|
||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -14,6 +14,37 @@ export const knownContentTypes: Record<ValidContentTypes, Content> = {
|
|||||||
"text/plain": "plain",
|
"text/plain": "plain",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ContentTypeTitle =
|
||||||
|
| "request.content_type_titles.text"
|
||||||
|
| "request.content_type_titles.structured"
|
||||||
|
| "request.content_type_titles.others"
|
||||||
|
|
||||||
|
type SegmentedContentType = {
|
||||||
|
title: ContentTypeTitle
|
||||||
|
contentTypes: ValidContentTypes[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const segmentedContentTypes: SegmentedContentType[] = [
|
||||||
|
{
|
||||||
|
title: "request.content_type_titles.text",
|
||||||
|
contentTypes: [
|
||||||
|
"application/json",
|
||||||
|
"application/ld+json",
|
||||||
|
"application/hal+json",
|
||||||
|
"application/vnd.api+json",
|
||||||
|
"application/xml",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "request.content_type_titles.structured",
|
||||||
|
contentTypes: ["application/x-www-form-urlencoded", "multipart/form-data"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "request.content_type_titles.others",
|
||||||
|
contentTypes: ["text/html", "text/plain"],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
export function isJSONContentType(contentType: string) {
|
export function isJSONContentType(contentType: string) {
|
||||||
return /\bjson\b/i.test(contentType)
|
return /\bjson\b/i.test(contentType)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"action": {
|
"action": {
|
||||||
"cancel": "取消",
|
"cancel": "取消",
|
||||||
"choose_file": "选择一个文件",
|
"choose_file": "选择文件",
|
||||||
"clear": "清除",
|
"clear": "清除",
|
||||||
"clear_all": "全部清除",
|
"clear_all": "全部清除",
|
||||||
"connect": "连接",
|
"connect": "连接",
|
||||||
@@ -9,18 +9,18 @@
|
|||||||
"delete": "删除",
|
"delete": "删除",
|
||||||
"disconnect": "断开连接",
|
"disconnect": "断开连接",
|
||||||
"dismiss": "忽略",
|
"dismiss": "忽略",
|
||||||
"dont_save": "Don't save",
|
"dont_save": "不保存",
|
||||||
"download_file": "下载文件",
|
"download_file": "下载文件",
|
||||||
"duplicate": "复制",
|
"duplicate": "复制",
|
||||||
"edit": "编辑",
|
"edit": "编辑",
|
||||||
"go_back": "返回",
|
"go_back": "返回",
|
||||||
"label": "标签",
|
"label": "标签",
|
||||||
"learn_more": "了解更多",
|
"learn_more": "了解更多",
|
||||||
"less": "Less",
|
"less": "更少",
|
||||||
"more": "更多",
|
"more": "更多",
|
||||||
"new": "新增",
|
"new": "新增",
|
||||||
"no": "否",
|
"no": "否",
|
||||||
"paste": "Paste",
|
"paste": "粘贴",
|
||||||
"prettify": "美化",
|
"prettify": "美化",
|
||||||
"remove": "移除",
|
"remove": "移除",
|
||||||
"restore": "恢复",
|
"restore": "恢复",
|
||||||
@@ -45,9 +45,9 @@
|
|||||||
"chat_with_us": "与我们交谈",
|
"chat_with_us": "与我们交谈",
|
||||||
"contact_us": "联系我们",
|
"contact_us": "联系我们",
|
||||||
"copy": "复制",
|
"copy": "复制",
|
||||||
"copy_user_id": "Copy User Auth Token",
|
"copy_user_id": "复制认证 Token",
|
||||||
"developer_option": "Developer options",
|
"developer_option": "开发者选项",
|
||||||
"developer_option_description": "Developer tools which helps in development and maintenance of Hoppscotch.",
|
"developer_option_description": "开发者工具,有助于开发和维护 Hoppscotch。",
|
||||||
"discord": "Discord",
|
"discord": "Discord",
|
||||||
"documentation": "帮助文档",
|
"documentation": "帮助文档",
|
||||||
"github": "GitHub",
|
"github": "GitHub",
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
"keyboard_shortcuts": "键盘快捷键",
|
"keyboard_shortcuts": "键盘快捷键",
|
||||||
"name": "Hoppscotch",
|
"name": "Hoppscotch",
|
||||||
"new_version_found": "已发现新版本。刷新页面以更新。",
|
"new_version_found": "已发现新版本。刷新页面以更新。",
|
||||||
"options": "Options",
|
"options": "选项",
|
||||||
"proxy_privacy_policy": "代理隐私政策",
|
"proxy_privacy_policy": "代理隐私政策",
|
||||||
"reload": "重新加载",
|
"reload": "重新加载",
|
||||||
"search": "搜索",
|
"search": "搜索",
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
"shortcuts": "快捷方式",
|
"shortcuts": "快捷方式",
|
||||||
"spotlight": "聚光灯",
|
"spotlight": "聚光灯",
|
||||||
"status": "状态",
|
"status": "状态",
|
||||||
"status_description": "Check the status of the website",
|
"status_description": "检查网站状态",
|
||||||
"terms_and_privacy": "隐私条款",
|
"terms_and_privacy": "隐私条款",
|
||||||
"twitter": "Twitter",
|
"twitter": "Twitter",
|
||||||
"type_a_command_search": "输入命令或搜索内容……",
|
"type_a_command_search": "输入命令或搜索内容……",
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
"continue_with_email": "使用电子邮箱登录",
|
"continue_with_email": "使用电子邮箱登录",
|
||||||
"continue_with_github": "使用 GitHub 登录",
|
"continue_with_github": "使用 GitHub 登录",
|
||||||
"continue_with_google": "使用 Google 登录",
|
"continue_with_google": "使用 Google 登录",
|
||||||
"continue_with_microsoft": "Continue with Microsoft",
|
"continue_with_microsoft": "使用 Microsoft 登录",
|
||||||
"email": "电子邮箱地址",
|
"email": "电子邮箱地址",
|
||||||
"logged_out": "登出",
|
"logged_out": "登出",
|
||||||
"login": "登录",
|
"login": "登录",
|
||||||
@@ -106,32 +106,32 @@
|
|||||||
"username": "用户名"
|
"username": "用户名"
|
||||||
},
|
},
|
||||||
"collection": {
|
"collection": {
|
||||||
"created": "组合已创建",
|
"created": "集合已创建",
|
||||||
"edit": "编辑组合",
|
"edit": "编辑集合",
|
||||||
"invalid_name": "请提供有效的组合名称",
|
"invalid_name": "请提供有效的集合名称",
|
||||||
"my_collections": "我的组合",
|
"my_collections": "我的集合",
|
||||||
"name": "我的新组合",
|
"name": "我的新集合",
|
||||||
"name_length_insufficient": "Collection name should be at least 3 characters long",
|
"name_length_insufficient": "集合名字至少需要 3 个字符",
|
||||||
"new": "新建组合",
|
"new": "新建集合",
|
||||||
"renamed": "组合已更名",
|
"renamed": "集合已更名",
|
||||||
"request_in_use": "Request in use",
|
"request_in_use": "请求正在使用中",
|
||||||
"save_as": "另存为",
|
"save_as": "另存为",
|
||||||
"select": "选择一个组合",
|
"select": "选择一个集合",
|
||||||
"select_location": "选择位置",
|
"select_location": "选择位置",
|
||||||
"select_team": "选择一个团队",
|
"select_team": "选择一个团队",
|
||||||
"team_collections": "团队组合"
|
"team_collections": "团队集合"
|
||||||
},
|
},
|
||||||
"confirm": {
|
"confirm": {
|
||||||
"exit_team": "你确定要离开此团队吗?",
|
"exit_team": "你确定要离开此团队吗?",
|
||||||
"logout": "你确定要登出吗?",
|
"logout": "你确定要登出吗?",
|
||||||
"remove_collection": "你确定要永久删除该组合吗?",
|
"remove_collection": "你确定要永久删除该集合吗?",
|
||||||
"remove_environment": "你确定要永久删除该环境吗?",
|
"remove_environment": "你确定要永久删除该环境吗?",
|
||||||
"remove_folder": "你确定要永久删除该文件夹吗?",
|
"remove_folder": "你确定要永久删除该文件夹吗?",
|
||||||
"remove_history": "你确定要永久删除全部历史记录吗?",
|
"remove_history": "你确定要永久删除全部历史记录吗?",
|
||||||
"remove_request": "你确定要永久删除该请求吗?",
|
"remove_request": "你确定要永久删除该请求吗?",
|
||||||
"remove_team": "你确定要删除该团队吗?",
|
"remove_team": "你确定要删除该团队吗?",
|
||||||
"remove_telemetry": "你确定要退出遥测服务吗?",
|
"remove_telemetry": "你确定要退出遥测服务吗?",
|
||||||
"request_change": "Are you sure you want to discard current request, unsaved changes will be lost.",
|
"request_change": "你确定你要放弃当前的请求,未保存的修改将被丢失。",
|
||||||
"sync": "您确定要同步该工作区吗?"
|
"sync": "您确定要同步该工作区吗?"
|
||||||
},
|
},
|
||||||
"count": {
|
"count": {
|
||||||
@@ -144,13 +144,13 @@
|
|||||||
},
|
},
|
||||||
"documentation": {
|
"documentation": {
|
||||||
"generate": "生成文档",
|
"generate": "生成文档",
|
||||||
"generate_message": "导入 Hoppscotch 组合以随时随地生成 API 文档。"
|
"generate_message": "导入 Hoppscotch 集合以随时随地生成 API 文档。"
|
||||||
},
|
},
|
||||||
"empty": {
|
"empty": {
|
||||||
"authorization": "该请求没有使用任何授权",
|
"authorization": "该请求没有使用任何授权",
|
||||||
"body": "该请求没有任何请求体",
|
"body": "该请求没有任何请求体",
|
||||||
"collection": "组合为空",
|
"collection": "集合为空",
|
||||||
"collections": "组合为空",
|
"collections": "集合为空",
|
||||||
"documentation": "连接至 GraphQL 端点以查看文档",
|
"documentation": "连接至 GraphQL 端点以查看文档",
|
||||||
"endpoint": "端点不能为空",
|
"endpoint": "端点不能为空",
|
||||||
"environments": "环境为空",
|
"environments": "环境为空",
|
||||||
@@ -169,20 +169,20 @@
|
|||||||
"tests": "没有针对该请求的测试"
|
"tests": "没有针对该请求的测试"
|
||||||
},
|
},
|
||||||
"environment": {
|
"environment": {
|
||||||
"add_to_global": "Add to Global",
|
"add_to_global": "添加到全局环境",
|
||||||
"added": "Environment addition",
|
"added": "环境已添加",
|
||||||
"create_new": "已创建新环境",
|
"create_new": "创建新环境",
|
||||||
"created": "Environment created",
|
"created": "环境已创建",
|
||||||
"deleted": "Environment deletion",
|
"deleted": "环境已删除",
|
||||||
"edit": "编辑环境",
|
"edit": "编辑环境",
|
||||||
"invalid_name": "请提供有效的环境名称",
|
"invalid_name": "请提供有效的环境名称",
|
||||||
"nested_overflow": "nested environment variables are limited to 10 levels",
|
"nested_overflow": "环境嵌套深度超过限制(10层)",
|
||||||
"new": "新建环境",
|
"new": "新建环境",
|
||||||
"no_environment": "无环境",
|
"no_environment": "无环境",
|
||||||
"no_environment_description": "No environments were selected. Choose what to do with the following variables.",
|
"no_environment_description": "没有选择环境。选择如何处理以下变量。",
|
||||||
"select": "选择环境",
|
"select": "选择环境",
|
||||||
"title": "环境",
|
"title": "环境",
|
||||||
"updated": "Environment updation",
|
"updated": "环境已更新",
|
||||||
"variable_list": "变量列表"
|
"variable_list": "变量列表"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
@@ -190,9 +190,9 @@
|
|||||||
"check_console_details": "检查控制台日志以获悉详情",
|
"check_console_details": "检查控制台日志以获悉详情",
|
||||||
"curl_invalid_format": "cURL 格式不正确",
|
"curl_invalid_format": "cURL 格式不正确",
|
||||||
"empty_req_name": "空请求名称",
|
"empty_req_name": "空请求名称",
|
||||||
"f12_details": "(F12 详情)",
|
"f12_details": "(F12 详情)",
|
||||||
"gql_prettify_invalid_query": "无法美化无效的查询,处理查询语法错误并重试",
|
"gql_prettify_invalid_query": "无法美化无效的查询,处理查询语法错误并重试",
|
||||||
"incomplete_config_urls": "Incomplete configuration URLs",
|
"incomplete_config_urls": "配置文件中的 URL 无效",
|
||||||
"incorrect_email": "电子邮箱错误",
|
"incorrect_email": "电子邮箱错误",
|
||||||
"invalid_link": "无效链接",
|
"invalid_link": "无效链接",
|
||||||
"invalid_link_description": "你点击的链接无效或已过期。",
|
"invalid_link_description": "你点击的链接无效或已过期。",
|
||||||
@@ -202,7 +202,7 @@
|
|||||||
"no_duration": "无持续时间",
|
"no_duration": "无持续时间",
|
||||||
"script_fail": "无法执行预请求脚本",
|
"script_fail": "无法执行预请求脚本",
|
||||||
"something_went_wrong": "发生了一些错误",
|
"something_went_wrong": "发生了一些错误",
|
||||||
"test_script_fail": "Could not execute post-request script"
|
"test_script_fail": "无法执行请求脚本"
|
||||||
},
|
},
|
||||||
"export": {
|
"export": {
|
||||||
"as_json": "导出为 JSON",
|
"as_json": "导出为 JSON",
|
||||||
@@ -215,7 +215,7 @@
|
|||||||
"created": "已创建文件夹",
|
"created": "已创建文件夹",
|
||||||
"edit": "编辑文件夹",
|
"edit": "编辑文件夹",
|
||||||
"invalid_name": "请提供文件夹的名称",
|
"invalid_name": "请提供文件夹的名称",
|
||||||
"name_length_insufficient": "Folder name should be at least 3 characters long",
|
"name_length_insufficient": "文件夹名称应至少为 3 个字符",
|
||||||
"new": "新文件夹",
|
"new": "新文件夹",
|
||||||
"renamed": "文件夹已更名"
|
"renamed": "文件夹已更名"
|
||||||
},
|
},
|
||||||
@@ -238,46 +238,46 @@
|
|||||||
"post_request_tests": "测试脚本使用 JavaScript 编写,并在收到响应后执行。",
|
"post_request_tests": "测试脚本使用 JavaScript 编写,并在收到响应后执行。",
|
||||||
"pre_request_script": "预请求脚本使用 JavaScript 编写,并在请求发送前执行。",
|
"pre_request_script": "预请求脚本使用 JavaScript 编写,并在请求发送前执行。",
|
||||||
"script_fail": "预请求脚本中似乎存在故障。 检查下面的错误并相应地修复脚本。",
|
"script_fail": "预请求脚本中似乎存在故障。 检查下面的错误并相应地修复脚本。",
|
||||||
"test_script_fail": "There seems to be an error with test script. Please fix the errors and run tests again",
|
"test_script_fail": "测试脚本似乎有一个错误。请修复错误并再次运行测试",
|
||||||
"tests": "编写测试脚本以自动调试。"
|
"tests": "编写测试脚本以自动调试。"
|
||||||
},
|
},
|
||||||
"hide": {
|
"hide": {
|
||||||
"collection": "Collapse Collection Panel",
|
"collection": "隐藏集合",
|
||||||
"more": "隐藏更多",
|
"more": "隐藏更多",
|
||||||
"preview": "隐藏预览",
|
"preview": "隐藏预览",
|
||||||
"sidebar": "隐藏侧边栏"
|
"sidebar": "隐藏侧边栏"
|
||||||
},
|
},
|
||||||
"import": {
|
"import": {
|
||||||
"collections": "导入组合",
|
"collections": "导入集合",
|
||||||
"curl": "导入 cURL",
|
"curl": "导入 cURL",
|
||||||
"failed": "导入失败",
|
"failed": "导入失败",
|
||||||
"from_gist": "从 Gist 导入",
|
"from_gist": "从 Gist 导入",
|
||||||
"from_gist_description": "Import from Gist URL",
|
"from_gist_description": "从 Gist URL 导入",
|
||||||
"from_insomnia": "Import from Insomnia",
|
"from_insomnia": "从 Insomnia 导入",
|
||||||
"from_insomnia_description": "Import from Insomnia collection",
|
"from_insomnia_description": "从 Insomnia 集合中导入",
|
||||||
"from_json": "Import from Hoppscotch",
|
"from_json": "从 Hoppscotch 导入",
|
||||||
"from_json_description": "Import from Hoppscotch collection file",
|
"from_json_description": "从 Hoppscotch 集合中导入",
|
||||||
"from_my_collections": "从我的组合导入",
|
"from_my_collections": "从我的集合导入",
|
||||||
"from_my_collections_description": "Import from My Collections file",
|
"from_my_collections_description": "从我的集合文件导入",
|
||||||
"from_openapi": "Import from OpenAPI",
|
"from_openapi": "从 OpenAPI 导入",
|
||||||
"from_openapi_description": "Import from OpenAPI specification file (YML/JSON)",
|
"from_openapi_description": "从 OpenAPI 文件导入(YML/JSON)",
|
||||||
"from_postman": "Import from Postman",
|
"from_postman": "从 Postman 导入",
|
||||||
"from_postman_description": "Import from Postman collection",
|
"from_postman_description": "从 Postman 集合中导入",
|
||||||
"from_url": "Import from URL",
|
"from_url": "从 URL 导入",
|
||||||
"gist_url": "输入 Gist URL",
|
"gist_url": "输入 Gist URL",
|
||||||
"json_description": "Import collections from a Hoppscotch Collections JSON file",
|
"json_description": "从 Hoppscotch 的集合文件导入(JSON)",
|
||||||
"title": "导入"
|
"title": "导入"
|
||||||
},
|
},
|
||||||
"layout": {
|
"layout": {
|
||||||
"collapse_collection": "Collapse or Expand Collections",
|
"collapse_collection": "折叠/展开集合",
|
||||||
"collapse_sidebar": "Collapse or Expand the sidebar",
|
"collapse_sidebar": "折叠/展开边栏",
|
||||||
"column": "垂直布局",
|
"column": "垂直布局",
|
||||||
"name": "Layout",
|
"name": "布局",
|
||||||
"row": "水平布局",
|
"row": "水平布局",
|
||||||
"zen_mode": "禅意模式"
|
"zen_mode": "ZEN 模式"
|
||||||
},
|
},
|
||||||
"modal": {
|
"modal": {
|
||||||
"collections": "组合",
|
"collections": "集合",
|
||||||
"confirm": "确认",
|
"confirm": "确认",
|
||||||
"edit_request": "编辑请求",
|
"edit_request": "编辑请求",
|
||||||
"import_export": "导入/导出"
|
"import_export": "导入/导出"
|
||||||
@@ -315,12 +315,12 @@
|
|||||||
"email_verification_mail": "确认邮件已发送至你的邮箱,请点击链接以验证你的电子邮箱。",
|
"email_verification_mail": "确认邮件已发送至你的邮箱,请点击链接以验证你的电子邮箱。",
|
||||||
"no_permission": "你无权执行此操作。",
|
"no_permission": "你无权执行此操作。",
|
||||||
"owner": "所有者",
|
"owner": "所有者",
|
||||||
"owner_description": "所有者可以添加、编辑和删除请求、组合及团队成员。",
|
"owner_description": "所有者可以添加、编辑和删除请求、集合及团队成员。",
|
||||||
"roles": "角色",
|
"roles": "角色",
|
||||||
"roles_description": "角色用以控制共享组合的访问权限。",
|
"roles_description": "角色用以控制共享集合的访问权限。",
|
||||||
"updated": "档案已更新",
|
"updated": "档案已更新",
|
||||||
"viewer": "阅览者",
|
"viewer": "查看者",
|
||||||
"viewer_description": "阅览者只可查看与使用请求。"
|
"viewer_description": "查看者只可查看与使用请求。"
|
||||||
},
|
},
|
||||||
"remove": {
|
"remove": {
|
||||||
"star": "移除星标"
|
"star": "移除星标"
|
||||||
@@ -340,10 +340,10 @@
|
|||||||
"invalid_name": "请提供请求名称",
|
"invalid_name": "请提供请求名称",
|
||||||
"method": "方法",
|
"method": "方法",
|
||||||
"name": "请求名称",
|
"name": "请求名称",
|
||||||
"new": "New Request",
|
"new": "新请求",
|
||||||
"override": "Override",
|
"override": "覆盖",
|
||||||
"override_help": "Set <xmp>Content-Type</xmp> in Headers",
|
"override_help": "设置 <xmp>Content-Type</xmp> 头",
|
||||||
"overriden": "Overridden",
|
"overriden": "覆盖",
|
||||||
"parameter_list": "查询参数",
|
"parameter_list": "查询参数",
|
||||||
"parameters": "参数",
|
"parameters": "参数",
|
||||||
"path": "路径",
|
"path": "路径",
|
||||||
@@ -356,7 +356,7 @@
|
|||||||
"save_as": "另存为",
|
"save_as": "另存为",
|
||||||
"saved": "请求已保存",
|
"saved": "请求已保存",
|
||||||
"share": "分享",
|
"share": "分享",
|
||||||
"share_description": "Share Hoppscotch with your friends",
|
"share_description": "分享 Hoppscotch 给你的朋友",
|
||||||
"title": "请求",
|
"title": "请求",
|
||||||
"type": "请求类型",
|
"type": "请求类型",
|
||||||
"url": "URL",
|
"url": "URL",
|
||||||
@@ -396,7 +396,7 @@
|
|||||||
"extension_version": "扩展版本",
|
"extension_version": "扩展版本",
|
||||||
"extensions": "扩展",
|
"extensions": "扩展",
|
||||||
"extensions_use_toggle": "使用浏览器扩展发送请求(如果存在)",
|
"extensions_use_toggle": "使用浏览器扩展发送请求(如果存在)",
|
||||||
"follow": "Follow Us",
|
"follow": "关注我们",
|
||||||
"font_size": "字体大小",
|
"font_size": "字体大小",
|
||||||
"font_size_large": "大",
|
"font_size_large": "大",
|
||||||
"font_size_medium": "中",
|
"font_size_medium": "中",
|
||||||
@@ -417,7 +417,7 @@
|
|||||||
"reset_default": "重置为默认",
|
"reset_default": "重置为默认",
|
||||||
"sidebar_on_left": "侧边栏移至左侧",
|
"sidebar_on_left": "侧边栏移至左侧",
|
||||||
"sync": "同步",
|
"sync": "同步",
|
||||||
"sync_collections": "组合",
|
"sync_collections": "集合",
|
||||||
"sync_description": "这些设置会同步到云。",
|
"sync_description": "这些设置会同步到云。",
|
||||||
"sync_environments": "环境",
|
"sync_environments": "环境",
|
||||||
"sync_history": "历史",
|
"sync_history": "历史",
|
||||||
@@ -464,21 +464,21 @@
|
|||||||
"previous_method": "选择上一个方法",
|
"previous_method": "选择上一个方法",
|
||||||
"put_method": "选择 PUT 方法",
|
"put_method": "选择 PUT 方法",
|
||||||
"reset_request": "重置请求",
|
"reset_request": "重置请求",
|
||||||
"save_to_collections": "保存到组合",
|
"save_to_collections": "保存到集合",
|
||||||
"send_request": "发送请求",
|
"send_request": "发送请求",
|
||||||
"title": "请求"
|
"title": "请求"
|
||||||
},
|
},
|
||||||
"theme": {
|
"theme": {
|
||||||
"black": "Switch theme to black mode",
|
"black": "切换为黑色主题",
|
||||||
"dark": "Switch theme to dark mode",
|
"dark": "切换为深色主题",
|
||||||
"light": "Switch theme to light mode",
|
"light": "切换为浅色主题",
|
||||||
"system": "Switch theme to system mode",
|
"system": "切换为系统主题",
|
||||||
"title": "Theme"
|
"title": "主题"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"show": {
|
"show": {
|
||||||
"code": "显示代码",
|
"code": "显示代码",
|
||||||
"collection": "Expand Collection Panel",
|
"collection": "展开集合",
|
||||||
"more": "显示更多",
|
"more": "显示更多",
|
||||||
"sidebar": "显示侧边栏"
|
"sidebar": "显示侧边栏"
|
||||||
},
|
},
|
||||||
@@ -525,7 +525,7 @@
|
|||||||
"community": "提问与互助",
|
"community": "提问与互助",
|
||||||
"documentation": "阅读更多有关 Hoppscotch 的内容",
|
"documentation": "阅读更多有关 Hoppscotch 的内容",
|
||||||
"forum": "答疑解惑",
|
"forum": "答疑解惑",
|
||||||
"github": "Follow us on Github",
|
"github": "在 Github 关注我们",
|
||||||
"shortcuts": "更快浏览应用",
|
"shortcuts": "更快浏览应用",
|
||||||
"team": "与团队保持联系",
|
"team": "与团队保持联系",
|
||||||
"title": "支持",
|
"title": "支持",
|
||||||
@@ -534,7 +534,7 @@
|
|||||||
"tab": {
|
"tab": {
|
||||||
"authorization": "授权",
|
"authorization": "授权",
|
||||||
"body": "请求体",
|
"body": "请求体",
|
||||||
"collections": "组合",
|
"collections": "集合",
|
||||||
"documentation": "帮助文档",
|
"documentation": "帮助文档",
|
||||||
"headers": "请求头",
|
"headers": "请求头",
|
||||||
"history": "历史记录",
|
"history": "历史记录",
|
||||||
@@ -552,18 +552,18 @@
|
|||||||
"websocket": "WebSocket"
|
"websocket": "WebSocket"
|
||||||
},
|
},
|
||||||
"team": {
|
"team": {
|
||||||
"already_member": "你已经是此团队的成员。请联系你的团队所有人。",
|
"already_member": "你已经是此团队的成员。请联系你的团队者。",
|
||||||
"create_new": "创建新团队",
|
"create_new": "创建新团队",
|
||||||
"deleted": "团队已删除",
|
"deleted": "团队已删除",
|
||||||
"edit": "编辑团队",
|
"edit": "编辑团队",
|
||||||
"email": "电子邮箱",
|
"email": "电子邮箱",
|
||||||
"email_do_not_match": "邮箱无法与你的帐户信息匹配。请联系你的团队所有人。",
|
"email_do_not_match": "邮箱无法与你的帐户信息匹配。请联系你的团队者。",
|
||||||
"exit": "退出团队",
|
"exit": "退出团队",
|
||||||
"exit_disabled": "团队所有者无法退出团队",
|
"exit_disabled": "团队所有者无法退出团队",
|
||||||
"invalid_email_format": "电子邮箱格式无效",
|
"invalid_email_format": "电子邮箱格式无效",
|
||||||
"invalid_id": "无效的团队 ID,请联系你的团队所有人。",
|
"invalid_id": "无效的团队 ID,请联系你的团队者。",
|
||||||
"invalid_invite_link": "无效的邀请链接",
|
"invalid_invite_link": "无效的邀请链接",
|
||||||
"invalid_invite_link_description": "你点击的链接无效。请联系你的团队所有人。",
|
"invalid_invite_link_description": "你点击的链接无效。请联系你的团队者。",
|
||||||
"invalid_member_permission": "请为团队成员提供有效的权限",
|
"invalid_member_permission": "请为团队成员提供有效的权限",
|
||||||
"invite": "邀请",
|
"invite": "邀请",
|
||||||
"invite_more": "邀请更多成员",
|
"invite_more": "邀请更多成员",
|
||||||
@@ -578,8 +578,8 @@
|
|||||||
"login_to_continue": "登录以继续",
|
"login_to_continue": "登录以继续",
|
||||||
"login_to_continue_description": "你需要登录以加入团队",
|
"login_to_continue_description": "你需要登录以加入团队",
|
||||||
"logout_and_try_again": "登出并以其他帐户登录",
|
"logout_and_try_again": "登出并以其他帐户登录",
|
||||||
"member_has_invite": "此邮箱 ID 已有邀请。请联系你的团队所有人。",
|
"member_has_invite": "此邮箱 ID 已有邀请。请联系你的团队者。",
|
||||||
"member_not_found": "未找到成员。请联系你的团队所有人。",
|
"member_not_found": "未找到成员。请联系你的团队者。",
|
||||||
"member_removed": "用户已移除",
|
"member_removed": "用户已移除",
|
||||||
"member_role_updated": "用户角色已更新",
|
"member_role_updated": "用户角色已更新",
|
||||||
"members": "成员",
|
"members": "成员",
|
||||||
@@ -588,10 +588,10 @@
|
|||||||
"new": "新团队",
|
"new": "新团队",
|
||||||
"new_created": "已创建新团队",
|
"new_created": "已创建新团队",
|
||||||
"new_name": "我的新团队",
|
"new_name": "我的新团队",
|
||||||
"no_access": "你没有编辑组合的权限",
|
"no_access": "你没有编辑集合的权限",
|
||||||
"no_invite_found": "未找到邀请。请联系你的团队所有人。",
|
"no_invite_found": "未找到邀请。请联系你的团队者。",
|
||||||
"not_found": "Team not found. Contact your team owner.",
|
"not_found": "没有找到团队,请联系您的团队所有者。",
|
||||||
"not_valid_viewer": "你不是有效的阅览者。请联系你的团队所有人。",
|
"not_valid_viewer": "你不是有效的查看者。请联系你的团队者。",
|
||||||
"pending_invites": "待办邀请",
|
"pending_invites": "待办邀请",
|
||||||
"permissions": "权限",
|
"permissions": "权限",
|
||||||
"saved": "团队已保存",
|
"saved": "团队已保存",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"download_file": "Download file",
|
"download_file": "Download file",
|
||||||
"duplicate": "Duplicate",
|
"duplicate": "Duplicate",
|
||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
|
"filter_response": "Filter response",
|
||||||
"go_back": "Go back",
|
"go_back": "Go back",
|
||||||
"label": "Label",
|
"label": "Label",
|
||||||
"learn_more": "Learn more",
|
"learn_more": "Learn more",
|
||||||
@@ -202,9 +203,11 @@
|
|||||||
"invalid_link": "Invalid link",
|
"invalid_link": "Invalid link",
|
||||||
"invalid_link_description": "The link you clicked is invalid or expired.",
|
"invalid_link_description": "The link you clicked is invalid or expired.",
|
||||||
"json_prettify_invalid_body": "Couldn't prettify an invalid body, solve json syntax errors and try again",
|
"json_prettify_invalid_body": "Couldn't prettify an invalid body, solve json syntax errors and try again",
|
||||||
|
"json_parsing_failed": "Invalid JSON",
|
||||||
"network_error": "There seems to be a network error. Please try again.",
|
"network_error": "There seems to be a network error. Please try again.",
|
||||||
"network_fail": "Could not send request",
|
"network_fail": "Could not send request",
|
||||||
"no_duration": "No duration",
|
"no_duration": "No duration",
|
||||||
|
"no_results_found": "No matches found",
|
||||||
"script_fail": "Could not execute pre-request script",
|
"script_fail": "Could not execute pre-request script",
|
||||||
"something_went_wrong": "Something went wrong",
|
"something_went_wrong": "Something went wrong",
|
||||||
"test_script_fail": "Could not execute post-request script"
|
"test_script_fail": "Could not execute post-request script"
|
||||||
@@ -340,6 +343,11 @@
|
|||||||
"body": "Request Body",
|
"body": "Request Body",
|
||||||
"choose_language": "Choose language",
|
"choose_language": "Choose language",
|
||||||
"content_type": "Content Type",
|
"content_type": "Content Type",
|
||||||
|
"content_type_titles": {
|
||||||
|
"others": "Others",
|
||||||
|
"structured": "Structured",
|
||||||
|
"text": "Text"
|
||||||
|
},
|
||||||
"copy_link": "Copy link",
|
"copy_link": "Copy link",
|
||||||
"duration": "Duration",
|
"duration": "Duration",
|
||||||
"enter_curl": "Enter cURL",
|
"enter_curl": "Enter cURL",
|
||||||
@@ -374,6 +382,7 @@
|
|||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"body": "Response Body",
|
"body": "Response Body",
|
||||||
|
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
||||||
"headers": "Headers",
|
"headers": "Headers",
|
||||||
"html": "HTML",
|
"html": "HTML",
|
||||||
"image": "Image",
|
"image": "Image",
|
||||||
|
|||||||
@@ -2,16 +2,16 @@
|
|||||||
"action": {
|
"action": {
|
||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
"choose_file": "Escolha um arquivo",
|
"choose_file": "Escolha um arquivo",
|
||||||
"clear": "Claro",
|
"clear": "Limpar",
|
||||||
"clear_all": "Limpar tudo",
|
"clear_all": "Limpar tudo",
|
||||||
"connect": "Conectar",
|
"connect": "Conectar",
|
||||||
"copy": "Copiar",
|
"copy": "Copiar",
|
||||||
"delete": "Excluir",
|
"delete": "Excluir",
|
||||||
"disconnect": "desconectar",
|
"disconnect": "Desconectar",
|
||||||
"dismiss": "Dispensar",
|
"dismiss": "Dispensar",
|
||||||
"dont_save": "Don't save",
|
"dont_save": "Não Salvar",
|
||||||
"download_file": "⇬ Fazer download do arquivo",
|
"download_file": "⇬ Fazer download do arquivo",
|
||||||
"duplicate": "Duplicate",
|
"duplicate": "Duplicar",
|
||||||
"edit": "Editar",
|
"edit": "Editar",
|
||||||
"go_back": "Voltar",
|
"go_back": "Voltar",
|
||||||
"label": "Etiqueta",
|
"label": "Etiqueta",
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
"turn_off": "Desligar",
|
"turn_off": "Desligar",
|
||||||
"turn_on": "Ligar",
|
"turn_on": "Ligar",
|
||||||
"undo": "Desfazer",
|
"undo": "Desfazer",
|
||||||
"yes": "sim"
|
"yes": "Sim"
|
||||||
},
|
},
|
||||||
"add": {
|
"add": {
|
||||||
"new": "Adicionar novo",
|
"new": "Adicionar novo",
|
||||||
@@ -45,9 +45,9 @@
|
|||||||
"chat_with_us": "Converse conosco",
|
"chat_with_us": "Converse conosco",
|
||||||
"contact_us": "Contate-Nos",
|
"contact_us": "Contate-Nos",
|
||||||
"copy": "Copiar",
|
"copy": "Copiar",
|
||||||
"copy_user_id": "Copy User Auth Token",
|
"copy_user_id": "Copiar token de autenticação do usuário",
|
||||||
"developer_option": "Developer options",
|
"developer_option": "Opções de desenvolvedor",
|
||||||
"developer_option_description": "Developer tools which helps in development and maintenance of Hoppscotch.",
|
"developer_option_description": "Opções de desenvolvedor que ajudam no desenvolvimento e manutenção do Hoppscotch.",
|
||||||
"discord": "Discord",
|
"discord": "Discord",
|
||||||
"documentation": "Documentação",
|
"documentation": "Documentação",
|
||||||
"github": "GitHub",
|
"github": "GitHub",
|
||||||
@@ -60,18 +60,18 @@
|
|||||||
"keyboard_shortcuts": "Atalhos do teclado",
|
"keyboard_shortcuts": "Atalhos do teclado",
|
||||||
"name": "Hoppscotch",
|
"name": "Hoppscotch",
|
||||||
"new_version_found": "Nova versão encontrada. Atualize para atualizar.",
|
"new_version_found": "Nova versão encontrada. Atualize para atualizar.",
|
||||||
"options": "Options",
|
"options": "Opções",
|
||||||
"proxy_privacy_policy": "Política de privacidade do proxy",
|
"proxy_privacy_policy": "Política de privacidade do proxy",
|
||||||
"reload": "recarregar",
|
"reload": "Recarregar",
|
||||||
"search": "Procurar",
|
"search": "Procurar",
|
||||||
"share": "Compartilhado",
|
"share": "Compartilhado",
|
||||||
"shortcuts": "Atalhos",
|
"shortcuts": "Atalhos",
|
||||||
"spotlight": "Holofote",
|
"spotlight": "Holofote",
|
||||||
"status": "Status",
|
"status": "Estado",
|
||||||
"status_description": "Check the status of the website",
|
"status_description": "Cheque o estado do website.",
|
||||||
"terms_and_privacy": "Termos e privacidade",
|
"terms_and_privacy": "Termos e privacidade",
|
||||||
"twitter": "Twitter",
|
"twitter": "Twitter",
|
||||||
"type_a_command_search": "Digite um comando ou pesquise ...",
|
"type_a_command_search": "Digite um comando ou pesquise...",
|
||||||
"we_use_cookies": "Usamos cookies",
|
"we_use_cookies": "Usamos cookies",
|
||||||
"whats_new": "O que há de novo?",
|
"whats_new": "O que há de novo?",
|
||||||
"wiki": "Wiki"
|
"wiki": "Wiki"
|
||||||
@@ -114,7 +114,7 @@
|
|||||||
"name_length_insufficient": "O nome da coleção deve ter pelo menos 3 caracteres",
|
"name_length_insufficient": "O nome da coleção deve ter pelo menos 3 caracteres",
|
||||||
"new": "Nova coleção",
|
"new": "Nova coleção",
|
||||||
"renamed": "Coleção renomeada",
|
"renamed": "Coleção renomeada",
|
||||||
"request_in_use": "Request in use",
|
"request_in_use": "Requisição em uso",
|
||||||
"save_as": "Salvar como",
|
"save_as": "Salvar como",
|
||||||
"select": "Selecione uma coleção",
|
"select": "Selecione uma coleção",
|
||||||
"select_location": "Selecione a localização",
|
"select_location": "Selecione a localização",
|
||||||
@@ -131,7 +131,7 @@
|
|||||||
"remove_request": "Tem certeza de que deseja excluir permanentemente esta solicitação?",
|
"remove_request": "Tem certeza de que deseja excluir permanentemente esta solicitação?",
|
||||||
"remove_team": "Tem certeza que deseja excluir esta equipe?",
|
"remove_team": "Tem certeza que deseja excluir esta equipe?",
|
||||||
"remove_telemetry": "Tem certeza de que deseja cancelar a telemetria?",
|
"remove_telemetry": "Tem certeza de que deseja cancelar a telemetria?",
|
||||||
"request_change": "Are you sure you want to discard current request, unsaved changes will be lost.",
|
"request_change": "Tem certeza que deseja descartar a requisição atual? Alterações não salvas serão perdidas.",
|
||||||
"sync": "Tem certeza de que deseja sincronizar este espaço de trabalho?"
|
"sync": "Tem certeza de que deseja sincronizar este espaço de trabalho?"
|
||||||
},
|
},
|
||||||
"count": {
|
"count": {
|
||||||
@@ -151,8 +151,8 @@
|
|||||||
"body": "Este pedido não tem corpo",
|
"body": "Este pedido não tem corpo",
|
||||||
"collection": "Coleção está vazia",
|
"collection": "Coleção está vazia",
|
||||||
"collections": "Coleções estão vazias",
|
"collections": "Coleções estão vazias",
|
||||||
"documentation": "Connect to a GraphQL endpoint to view documentation",
|
"documentation": "Se conecte à um endpoint GraphQL para ver a documentação",
|
||||||
"endpoint": "Endpoint cannot be empty",
|
"endpoint": "O endpoint não pode ser vazio",
|
||||||
"environments": "Ambientes estão vazios",
|
"environments": "Ambientes estão vazios",
|
||||||
"folder": "Pasta está vazia",
|
"folder": "Pasta está vazia",
|
||||||
"headers": "Esta solicitação não possui cabeçalhos",
|
"headers": "Esta solicitação não possui cabeçalhos",
|
||||||
@@ -172,11 +172,11 @@
|
|||||||
"add_to_global": "Adicionar ao Global",
|
"add_to_global": "Adicionar ao Global",
|
||||||
"added": "Adição de ambiente",
|
"added": "Adição de ambiente",
|
||||||
"create_new": "Crie um novo ambiente",
|
"create_new": "Crie um novo ambiente",
|
||||||
"created": "Environment created",
|
"created": "Ambiente criado",
|
||||||
"deleted": "Deleção de ambiente",
|
"deleted": "Deleção de ambiente",
|
||||||
"edit": "Editar Ambiente",
|
"edit": "Editar Ambiente",
|
||||||
"invalid_name": "Forneça um nome válido para o ambiente",
|
"invalid_name": "Forneça um nome válido para o ambiente",
|
||||||
"nested_overflow": "variáveis de ambiente aninhadas são limitadas a 10 níveis",
|
"nested_overflow": "Variáveis de ambiente aninhadas são limitadas a 10 níveis",
|
||||||
"new": "Novo ambiente",
|
"new": "Novo ambiente",
|
||||||
"no_environment": "Sem ambiente",
|
"no_environment": "Sem ambiente",
|
||||||
"no_environment_description": "Nenhum ambiente foi selecionado. Escolha o que fazer com as seguintes variáveis.",
|
"no_environment_description": "Nenhum ambiente foi selecionado. Escolha o que fazer com as seguintes variáveis.",
|
||||||
@@ -195,9 +195,9 @@
|
|||||||
"incomplete_config_urls": "URLs de configuração incompletas",
|
"incomplete_config_urls": "URLs de configuração incompletas",
|
||||||
"incorrect_email": "Email incorreto",
|
"incorrect_email": "Email incorreto",
|
||||||
"invalid_link": "Link inválido",
|
"invalid_link": "Link inválido",
|
||||||
"invalid_link_description": "The link you clicked is invalid or expired.",
|
"invalid_link_description": "O link que você clicou é inválido ou já expirou.",
|
||||||
"json_prettify_invalid_body": "Não foi possível embelezar um corpo inválido, resolver erros de sintaxe json e tentar novamente",
|
"json_prettify_invalid_body": "Não foi possível embelezar um corpo inválido, resolver erros de sintaxe json e tentar novamente",
|
||||||
"network_error": "There seems to be a network error. Please try again.",
|
"network_error": "Parece que houve um problema de rede. Por favor, tente novamente.",
|
||||||
"network_fail": "Não foi possível enviar requisição",
|
"network_fail": "Não foi possível enviar requisição",
|
||||||
"no_duration": "Sem duração",
|
"no_duration": "Sem duração",
|
||||||
"script_fail": "Não foi possível executar o script pré-requisição",
|
"script_fail": "Não foi possível executar o script pré-requisição",
|
||||||
@@ -252,25 +252,25 @@
|
|||||||
"curl": "Importar cURL",
|
"curl": "Importar cURL",
|
||||||
"failed": "A importação falhou",
|
"failed": "A importação falhou",
|
||||||
"from_gist": "Importar do Gist",
|
"from_gist": "Importar do Gist",
|
||||||
"from_gist_description": "Import from Gist URL",
|
"from_gist_description": "Importar de URL Gist",
|
||||||
"from_insomnia": "Import from Insomnia",
|
"from_insomnia": "Importar de Insomnia",
|
||||||
"from_insomnia_description": "Import from Insomnia collection",
|
"from_insomnia_description": "Importa de coleção Insomnia",
|
||||||
"from_json": "Import from Hoppscotch",
|
"from_json": "Importar de Hoppscotch",
|
||||||
"from_json_description": "Import from Hoppscotch collection file",
|
"from_json_description": "Importa de arquivo de coleção Hoppscotch",
|
||||||
"from_my_collections": "Importar de minhas coleções",
|
"from_my_collections": "Importar de minhas coleções",
|
||||||
"from_my_collections_description": "Import from My Collections file",
|
"from_my_collections_description": "Importa de arquivo Minhas Coleções",
|
||||||
"from_openapi": "Import from OpenAPI",
|
"from_openapi": "Importar de OpenAPI",
|
||||||
"from_openapi_description": "Import from OpenAPI specification file (YML/JSON)",
|
"from_openapi_description": "Importa de arquivo de especificação OpenAPI (YML/JSON)",
|
||||||
"from_postman": "Import from Postman",
|
"from_postman": "Importar de Postman",
|
||||||
"from_postman_description": "Import from Postman collection",
|
"from_postman_description": "Importa de coleção Postman",
|
||||||
"from_url": "Import from URL",
|
"from_url": "Importar de URL",
|
||||||
"gist_url": "Insira o URL da essência",
|
"gist_url": "Insira o URL do Gist",
|
||||||
"json_description": "Import collections from a Hoppscotch Collections JSON file",
|
"json_description": "Importa coleções de um arquivo JSON de Coleções Hoppscotch",
|
||||||
"title": "Importar"
|
"title": "Importar"
|
||||||
},
|
},
|
||||||
"layout": {
|
"layout": {
|
||||||
"collapse_collection": "Collapse or Expand Collections",
|
"collapse_collection": "Encolher ou expandir coleções",
|
||||||
"collapse_sidebar": "Collapse or Expand the sidebar",
|
"collapse_sidebar": "Encolher ou Expandir a barra lateral",
|
||||||
"column": "Layout vertical",
|
"column": "Layout vertical",
|
||||||
"name": "Layout",
|
"name": "Layout",
|
||||||
"row": "Layout horizontal",
|
"row": "Layout horizontal",
|
||||||
@@ -311,16 +311,16 @@
|
|||||||
"profile": {
|
"profile": {
|
||||||
"app_settings": "App Settings",
|
"app_settings": "App Settings",
|
||||||
"editor": "Editor",
|
"editor": "Editor",
|
||||||
"editor_description": "Editors can add, edit, and delete requests.",
|
"editor_description": "Editores podem adicionar, editar e deletar requisições.",
|
||||||
"email_verification_mail": "A verification email has been sent to your email address. Please click on the link to verify your email address.",
|
"email_verification_mail": "Um e-mail de verificação foi enviado ao seu endereço de e-mail. Por favor, clique no link para verificar seu endereço e-mail.",
|
||||||
"no_permission": "You do not have permission to perform this action.",
|
"no_permission": "Você não tem permissão para realizar esta ação.",
|
||||||
"owner": "Owner",
|
"owner": "Dono",
|
||||||
"owner_description": "Owners can add, edit, and delete requests, collections and team members.",
|
"owner_description": "Donos podem adicionar, editar e deletar requisições, coleções e membros de equipe.",
|
||||||
"roles": "Roles",
|
"roles": "Funções",
|
||||||
"roles_description": "Roles are used to control access to the shared collections.",
|
"roles_description": "Funções são utilizadas para gerenciar acesso às coleções compartilhadas.",
|
||||||
"updated": "Profile updated",
|
"updated": "Perfil atualizado",
|
||||||
"viewer": "Viewer",
|
"viewer": "Espectador",
|
||||||
"viewer_description": "Viewers can only view and use requests."
|
"viewer_description": "Espectadores só podem ver e usar requisições."
|
||||||
},
|
},
|
||||||
"remove": {
|
"remove": {
|
||||||
"star": "Remover estrela"
|
"star": "Remover estrela"
|
||||||
@@ -340,10 +340,10 @@
|
|||||||
"invalid_name": "Forneça um nome para a requisição",
|
"invalid_name": "Forneça um nome para a requisição",
|
||||||
"method": "Método",
|
"method": "Método",
|
||||||
"name": "Nome da requisição",
|
"name": "Nome da requisição",
|
||||||
"new": "New Request",
|
"new": "Nova requisição",
|
||||||
"override": "Override",
|
"override": "Substituir",
|
||||||
"override_help": "Set <xmp>Content-Type</xmp> in Headers",
|
"override_help": "Substituir <xmp>Content-Type</xmp> em Headers",
|
||||||
"overriden": "Overridden",
|
"overriden": "Substituído",
|
||||||
"parameter_list": "Parâmetros da requisição",
|
"parameter_list": "Parâmetros da requisição",
|
||||||
"parameters": "Parâmetros",
|
"parameters": "Parâmetros",
|
||||||
"path": "Caminho",
|
"path": "Caminho",
|
||||||
@@ -356,7 +356,7 @@
|
|||||||
"save_as": "Salvar como",
|
"save_as": "Salvar como",
|
||||||
"saved": "Requisição salva",
|
"saved": "Requisição salva",
|
||||||
"share": "Compartilhadar",
|
"share": "Compartilhadar",
|
||||||
"share_description": "Share Hoppscotch with your friends",
|
"share_description": "Compartilhe o Hoppscotch com seus amigos",
|
||||||
"title": "Solicitar",
|
"title": "Solicitar",
|
||||||
"type": "Tipo de requisição",
|
"type": "Tipo de requisição",
|
||||||
"url": "URL",
|
"url": "URL",
|
||||||
@@ -396,7 +396,7 @@
|
|||||||
"extension_version": "Versão da extensão",
|
"extension_version": "Versão da extensão",
|
||||||
"extensions": "Extensões",
|
"extensions": "Extensões",
|
||||||
"extensions_use_toggle": "Use a extensão do navegador para enviar solicitações (se houver)",
|
"extensions_use_toggle": "Use a extensão do navegador para enviar solicitações (se houver)",
|
||||||
"follow": "Follow Us",
|
"follow": "Nos siga",
|
||||||
"font_size": "Tamanho da fonte",
|
"font_size": "Tamanho da fonte",
|
||||||
"font_size_large": "Grande",
|
"font_size_large": "Grande",
|
||||||
"font_size_medium": "Médio",
|
"font_size_medium": "Médio",
|
||||||
@@ -407,7 +407,7 @@
|
|||||||
"light_mode": "Luz",
|
"light_mode": "Luz",
|
||||||
"official_proxy_hosting": "Official Proxy é hospedado por Hoppscotch.",
|
"official_proxy_hosting": "Official Proxy é hospedado por Hoppscotch.",
|
||||||
"profile": "Perfil",
|
"profile": "Perfil",
|
||||||
"profile_description": "Update your profile details",
|
"profile_description": "Atualize os detalhes de seu perfil",
|
||||||
"profile_email": "Endereço de email",
|
"profile_email": "Endereço de email",
|
||||||
"profile_name": "Nome do perfil",
|
"profile_name": "Nome do perfil",
|
||||||
"proxy": "Proxy",
|
"proxy": "Proxy",
|
||||||
|
|||||||
@@ -540,6 +540,6 @@ export function updateEnvironmentVariable(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getEnviroment(index: number) {
|
export function getEnvironment(index: number) {
|
||||||
return environmentsStore.value.environments[index]
|
return environmentsStore.value.environments[index]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ export default {
|
|||||||
// https://github.com/nuxt/typescript
|
// https://github.com/nuxt/typescript
|
||||||
["@nuxt/typescript-build", { typeCheck: false }],
|
["@nuxt/typescript-build", { typeCheck: false }],
|
||||||
// https://github.com/nuxt-community/dotenv-module
|
// https://github.com/nuxt-community/dotenv-module
|
||||||
"@nuxtjs/dotenv",
|
["@nuxtjs/dotenv", { systemvars: true }],
|
||||||
// https://github.com/nuxt-community/composition-api
|
// https://github.com/nuxt-community/composition-api
|
||||||
"@nuxtjs/composition-api/module",
|
"@nuxtjs/composition-api/module",
|
||||||
"~/modules/emit-volar-types.ts",
|
"~/modules/emit-volar-types.ts",
|
||||||
@@ -339,6 +339,8 @@ export default {
|
|||||||
APP_ID: process.env.APP_ID,
|
APP_ID: process.env.APP_ID,
|
||||||
MEASUREMENT_ID: process.env.MEASUREMENT_ID,
|
MEASUREMENT_ID: process.env.MEASUREMENT_ID,
|
||||||
BASE_URL: process.env.BASE_URL,
|
BASE_URL: process.env.BASE_URL,
|
||||||
|
BACKEND_GQL_URL: process.env.BACKEND_GQL_URL,
|
||||||
|
BACKEND_WS_URL: process.env.BACKEND_WS_URL,
|
||||||
},
|
},
|
||||||
|
|
||||||
publicRuntimeConfig: {
|
publicRuntimeConfig: {
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
"@codemirror/text": "^0.19.6",
|
"@codemirror/text": "^0.19.6",
|
||||||
"@codemirror/tooltip": "^0.19.16",
|
"@codemirror/tooltip": "^0.19.16",
|
||||||
"@codemirror/view": "^0.19.48",
|
"@codemirror/view": "^0.19.48",
|
||||||
"@hoppscotch/codemirror-lang-graphql": "workspace:^0.1.0",
|
"@hoppscotch/codemirror-lang-graphql": "workspace:^0.2.0",
|
||||||
"@hoppscotch/data": "workspace:^0.4.2",
|
"@hoppscotch/data": "workspace:^0.4.2",
|
||||||
"@hoppscotch/js-sandbox": "workspace:^2.0.0",
|
"@hoppscotch/js-sandbox": "workspace:^2.0.0",
|
||||||
"@nuxtjs/axios": "^5.13.6",
|
"@nuxtjs/axios": "^5.13.6",
|
||||||
@@ -88,6 +88,7 @@
|
|||||||
"io-ts": "^2.2.16",
|
"io-ts": "^2.2.16",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"json-loader": "^0.5.7",
|
"json-loader": "^0.5.7",
|
||||||
|
"jsonpath-plus": "^6.0.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"lossless-json": "^1.0.5",
|
"lossless-json": "^1.0.5",
|
||||||
"mustache": "^4.2.0",
|
"mustache": "^4.2.0",
|
||||||
|
|||||||
@@ -83,7 +83,7 @@
|
|||||||
<FirebaseLogout outline />
|
<FirebaseLogout outline />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<SmartTabs v-model="selectedProfileTab">
|
<SmartTabs v-model="selectedProfileTab" render-inactive-tabs>
|
||||||
<SmartTab :id="'sync'" :label="t('settings.account')">
|
<SmartTab :id="'sync'" :label="t('settings.account')">
|
||||||
<div class="grid grid-cols-1">
|
<div class="grid grid-cols-1">
|
||||||
<section class="p-4">
|
<section class="p-4">
|
||||||
|
|||||||
@@ -1,53 +1,67 @@
|
|||||||
<template>
|
<template>
|
||||||
<SmartTabs
|
<SmartTabs v-model="currentTab">
|
||||||
v-model="selectedNavigationTab"
|
|
||||||
class="h-full !overflow-hidden"
|
|
||||||
styles="sticky bg-primary top-0 z-10 border-b border-dividerLight !overflow-visible"
|
|
||||||
>
|
|
||||||
<SmartTab
|
<SmartTab
|
||||||
id="websocket"
|
v-for="{ target, title } in REALTIME_NAVIGATION"
|
||||||
:label="$t('tab.websocket')"
|
:id="target"
|
||||||
style="height: calc(100% - var(--sidebar-primary-sticky-fold))"
|
:key="target"
|
||||||
|
:label="title"
|
||||||
>
|
>
|
||||||
<RealtimeWebsocket />
|
<NuxtChild />
|
||||||
</SmartTab>
|
|
||||||
<SmartTab
|
|
||||||
id="sse"
|
|
||||||
:label="$t('tab.sse')"
|
|
||||||
style="height: calc(100% - var(--sidebar-primary-sticky-fold))"
|
|
||||||
>
|
|
||||||
<RealtimeSse />
|
|
||||||
</SmartTab>
|
|
||||||
<SmartTab
|
|
||||||
id="socketio"
|
|
||||||
:label="$t('tab.socketio')"
|
|
||||||
style="height: calc(100% - var(--sidebar-primary-sticky-fold))"
|
|
||||||
>
|
|
||||||
<RealtimeSocketio />
|
|
||||||
</SmartTab>
|
|
||||||
<SmartTab
|
|
||||||
id="mqtt"
|
|
||||||
:label="$t('tab.mqtt')"
|
|
||||||
style="height: calc(100% - var(--sidebar-primary-sticky-fold))"
|
|
||||||
>
|
|
||||||
<RealtimeMqtt />
|
|
||||||
</SmartTab>
|
</SmartTab>
|
||||||
</SmartTabs>
|
</SmartTabs>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import { defineComponent } from "@nuxtjs/composition-api"
|
import { watch, ref, useRouter, useRoute } from "@nuxtjs/composition-api"
|
||||||
|
import { useI18n } from "~/helpers/utils/composables"
|
||||||
|
|
||||||
export default defineComponent({
|
const t = useI18n()
|
||||||
data() {
|
const router = useRouter()
|
||||||
return {
|
const route = useRoute()
|
||||||
selectedNavigationTab: "websocket",
|
|
||||||
}
|
const REALTIME_NAVIGATION = [
|
||||||
|
{
|
||||||
|
target: "websocket",
|
||||||
|
title: t("tab.websocket"),
|
||||||
},
|
},
|
||||||
head() {
|
{
|
||||||
return {
|
target: "sse",
|
||||||
title: `${this.$t("navigation.realtime")} • Hoppscotch`,
|
title: t("tab.sse"),
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
target: "socketio",
|
||||||
|
title: t("tab.socketio"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target: "mqtt",
|
||||||
|
title: t("tab.mqtt"),
|
||||||
|
},
|
||||||
|
] as const
|
||||||
|
|
||||||
|
type RealtimeNavTab = typeof REALTIME_NAVIGATION[number]["target"]
|
||||||
|
|
||||||
|
const currentTab = ref<RealtimeNavTab>("websocket")
|
||||||
|
|
||||||
|
// Update the router when the tab is updated
|
||||||
|
watch(currentTab, (newTab) => {
|
||||||
|
router.push(`/realtime/${newTab}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Update the tab when router is upgrad
|
||||||
|
watch(
|
||||||
|
route,
|
||||||
|
(updateRoute) => {
|
||||||
|
if (updateRoute.path === "/realtime") router.replace("/realtime/websocket")
|
||||||
|
|
||||||
|
const destination: string | undefined =
|
||||||
|
updateRoute.path.split("/realtime/")[1]
|
||||||
|
|
||||||
|
const target = REALTIME_NAVIGATION.find(
|
||||||
|
({ target }) => target === destination
|
||||||
|
)?.target
|
||||||
|
|
||||||
|
if (target) currentTab.value = target
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -85,11 +85,13 @@
|
|||||||
|
|
||||||
<SmartTabs
|
<SmartTabs
|
||||||
v-model="selectedTab"
|
v-model="selectedTab"
|
||||||
styles="sticky bg-primary top-upperMobilePrimaryStickyFold sm:top-upperPrimaryStickyFold z-10"
|
styles="sticky bg-primary top-upperPrimaryStickyFold z-10"
|
||||||
|
render-inactive-tabs
|
||||||
>
|
>
|
||||||
<SmartTab
|
<SmartTab
|
||||||
:id="'communication'"
|
:id="'communication'"
|
||||||
:label="`${t('websocket.communication')}`"
|
:label="`${t('websocket.communication')}`"
|
||||||
|
render-inactive-tabs
|
||||||
>
|
>
|
||||||
<RealtimeCommunication
|
<RealtimeCommunication
|
||||||
:show-event-field="true"
|
:show-event-field="true"
|
||||||
@@ -99,7 +101,7 @@
|
|||||||
</SmartTab>
|
</SmartTab>
|
||||||
<SmartTab :id="'protocols'" :label="`${t('request.authorization')}`">
|
<SmartTab :id="'protocols'" :label="`${t('request.authorization')}`">
|
||||||
<div
|
<div
|
||||||
class="sticky z-10 flex items-center justify-between pl-4 border-b bg-primary border-dividerLight top-upperPrimaryStickyFold"
|
class="sticky z-10 flex items-center justify-between pl-4 border-b bg-primary border-dividerLight top-upperSecondaryStickyFold"
|
||||||
>
|
>
|
||||||
<span class="flex items-center">
|
<span class="flex items-center">
|
||||||
<label class="font-semibold text-secondaryLight">
|
<label class="font-semibold text-secondaryLight">
|
||||||
@@ -37,7 +37,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<SmartTabs
|
<SmartTabs
|
||||||
v-model="selectedTab"
|
v-model="selectedTab"
|
||||||
styles="sticky bg-primary top-upperMobilePrimaryStickyFold sm:top-upperPrimaryStickyFold z-10"
|
styles="sticky bg-primary top-upperPrimaryStickyFold z-10"
|
||||||
|
render-inactive-tabs
|
||||||
>
|
>
|
||||||
<SmartTab
|
<SmartTab
|
||||||
:id="'communication'"
|
:id="'communication'"
|
||||||
@@ -50,7 +51,7 @@
|
|||||||
</SmartTab>
|
</SmartTab>
|
||||||
<SmartTab :id="'protocols'" :label="`${$t('websocket.protocols')}`">
|
<SmartTab :id="'protocols'" :label="`${$t('websocket.protocols')}`">
|
||||||
<div
|
<div
|
||||||
class="sticky z-10 flex items-center justify-between pl-4 border-b bg-primary border-dividerLight top-upperPrimaryStickyFold"
|
class="sticky z-10 flex items-center justify-between pl-4 border-b bg-primary border-dividerLight top-upperSecondaryStickyFold"
|
||||||
>
|
>
|
||||||
<label class="font-semibold text-secondaryLight">
|
<label class="font-semibold text-secondaryLight">
|
||||||
{{ t("websocket.protocols") }}
|
{{ t("websocket.protocols") }}
|
||||||
@@ -236,6 +236,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch, defineComponent } from "@nuxtjs/composition-api"
|
import { ref, computed, watch, defineComponent } from "@nuxtjs/composition-api"
|
||||||
|
import { refAutoReset } from "@vueuse/core"
|
||||||
import { applySetting, toggleSetting, useSetting } from "~/newstore/settings"
|
import { applySetting, toggleSetting, useSetting } from "~/newstore/settings"
|
||||||
import {
|
import {
|
||||||
useToast,
|
useToast,
|
||||||
@@ -276,7 +277,7 @@ const hasFirefoxExtInstalled = computed(
|
|||||||
() => browserIsFirefox() && currentExtensionStatus.value === "available"
|
() => browserIsFirefox() && currentExtensionStatus.value === "available"
|
||||||
)
|
)
|
||||||
|
|
||||||
const clearIcon = ref("rotate-ccw")
|
const clearIcon = refAutoReset<"rotate-ccw" | "check">("rotate-ccw", 1000)
|
||||||
|
|
||||||
const confirmRemove = ref(false)
|
const confirmRemove = ref(false)
|
||||||
|
|
||||||
@@ -322,7 +323,6 @@ const resetProxy = () => {
|
|||||||
applySetting("PROXY_URL", `https://proxy.hoppscotch.io/`)
|
applySetting("PROXY_URL", `https://proxy.hoppscotch.io/`)
|
||||||
clearIcon.value = "check"
|
clearIcon.value = "check"
|
||||||
toast.success(`${t("state.cleared")}`)
|
toast.success(`${t("state.cleared")}`)
|
||||||
setTimeout(() => (clearIcon.value = "rotate-ccw"), 1000)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getColorModeName = (colorMode: string) => {
|
const getColorModeName = (colorMode: string) => {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
|
"jsx": "preserve",
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
@@ -29,6 +30,7 @@
|
|||||||
},
|
},
|
||||||
"exclude": ["node_modules", ".nuxt", "dist"],
|
"exclude": ["node_modules", ".nuxt", "dist"],
|
||||||
"vueCompilerOptions": {
|
"vueCompilerOptions": {
|
||||||
|
"target": 2,
|
||||||
"experimentalCompatMode": 2
|
"experimentalCompatMode": 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
6
packages/hoppscotch-app/types/jsonpath-plus.d.ts
vendored
Normal file
6
packages/hoppscotch-app/types/jsonpath-plus.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { JSONPathOptions } from "jsonpath-plus"
|
||||||
|
|
||||||
|
declare module "jsonpath-plus" {
|
||||||
|
export type JSONPathType = (options: JSONPathOptions) => unknown
|
||||||
|
export const JSONPath: JSONPathType
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ export default defineConfig({
|
|||||||
"var(--upper-mobile-raw-tertiary-sticky-fold)",
|
"var(--upper-mobile-raw-tertiary-sticky-fold)",
|
||||||
lowerPrimaryStickyFold: "var(--lower-primary-sticky-fold)",
|
lowerPrimaryStickyFold: "var(--lower-primary-sticky-fold)",
|
||||||
lowerSecondaryStickyFold: "var(--lower-secondary-sticky-fold)",
|
lowerSecondaryStickyFold: "var(--lower-secondary-sticky-fold)",
|
||||||
|
lowerTertiaryStickyFold: "var(--lower-tertiary-sticky-fold)",
|
||||||
sidebarPrimaryStickyFold: "var(--sidebar-primary-sticky-fold)",
|
sidebarPrimaryStickyFold: "var(--sidebar-primary-sticky-fold)",
|
||||||
},
|
},
|
||||||
colors: {
|
colors: {
|
||||||
|
|||||||
@@ -24,13 +24,26 @@ hopp [options or commands] arguments
|
|||||||
|
|
||||||
- Displays the help text
|
- Displays the help text
|
||||||
|
|
||||||
3. #### **`hopp test <file_path>`**
|
3. #### **`hopp test [options] <file_path>`**
|
||||||
- Interactive CLI to accept Hoppscotch collection JSON path
|
- Interactive CLI to accept Hoppscotch collection JSON path
|
||||||
- Parses the collection JSON and executes each requests
|
- Parses the collection JSON and executes each requests
|
||||||
- Executes pre-request script.
|
- Executes pre-request script.
|
||||||
- Outputs the response of each request.
|
- Outputs the response of each request.
|
||||||
- Executes and outputs test-script response.
|
- Executes and outputs test-script response.
|
||||||
|
|
||||||
|
#### Options:
|
||||||
|
##### `-e <file_path>` / `--env <file_path>`
|
||||||
|
- Accepts path to env.json with contents in below format:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ENV1":"value1",
|
||||||
|
"ENV2":"value2"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- You can now access those variables using `pw.env.get('<var_name>')`
|
||||||
|
|
||||||
|
Taking the above example, `pw.env.get("ENV1")` will return `"value1"`
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
Install [@hoppscotch/cli](https://www.npmjs.com/package/@hoppscotch/cli) from npm by running:
|
Install [@hoppscotch/cli](https://www.npmjs.com/package/@hoppscotch/cli) from npm by running:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@hoppscotch/cli",
|
"name": "@hoppscotch/cli",
|
||||||
"version": "0.1.14",
|
"version": "0.2.1",
|
||||||
"description": "A CLI to run Hoppscotch test scripts in CI environments.",
|
"description": "A CLI to run Hoppscotch test scripts in CI environments.",
|
||||||
"homepage": "https://hoppscotch.io",
|
"homepage": "https://hoppscotch.io",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ describe("Test 'hopp test <file>' command:", () => {
|
|||||||
const { stdout } = await execAsync(cmd);
|
const { stdout } = await execAsync(cmd);
|
||||||
const out = getErrorCode(stdout);
|
const out = getErrorCode(stdout);
|
||||||
|
|
||||||
expect(out).toBe<HoppErrorCode>("NO_FILE_PATH");
|
expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Collection file not found.", async () => {
|
test("Collection file not found.", async () => {
|
||||||
@@ -42,7 +42,7 @@ describe("Test 'hopp test <file>' command:", () => {
|
|||||||
const { stdout } = await execAsync(cmd);
|
const { stdout } = await execAsync(cmd);
|
||||||
const out = getErrorCode(stdout);
|
const out = getErrorCode(stdout);
|
||||||
|
|
||||||
expect(out).toBe<HoppErrorCode>("FILE_NOT_JSON");
|
expect(out).toBe<HoppErrorCode>("INVALID_FILE_TYPE");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Some errors occured (exit code 1).", async () => {
|
test("Some errors occured (exit code 1).", async () => {
|
||||||
@@ -62,3 +62,42 @@ describe("Test 'hopp test <file>' command:", () => {
|
|||||||
expect(error).toBeNull();
|
expect(error).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Test 'hopp test <file> --env <file>' command:", () => {
|
||||||
|
const VALID_TEST_CMD = `node ./bin/hopp test ${getTestJsonFilePath(
|
||||||
|
"passes.json"
|
||||||
|
)}`;
|
||||||
|
|
||||||
|
test("No env file path provided.", async () => {
|
||||||
|
const cmd = `${VALID_TEST_CMD} --env`;
|
||||||
|
const { stdout } = await execAsync(cmd);
|
||||||
|
const out = getErrorCode(stdout);
|
||||||
|
|
||||||
|
expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("ENV file not JSON type.", async () => {
|
||||||
|
const cmd = `${VALID_TEST_CMD} --env ${getTestJsonFilePath("notjson.txt")}`;
|
||||||
|
const { stdout } = await execAsync(cmd);
|
||||||
|
const out = getErrorCode(stdout);
|
||||||
|
|
||||||
|
expect(out).toBe<HoppErrorCode>("INVALID_FILE_TYPE");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("ENV file not found.", async () => {
|
||||||
|
const cmd = `${VALID_TEST_CMD} --env notfound.json`;
|
||||||
|
const { stdout } = await execAsync(cmd);
|
||||||
|
const out = getErrorCode(stdout);
|
||||||
|
|
||||||
|
expect(out).toBe<HoppErrorCode>("FILE_NOT_FOUND");
|
||||||
|
});
|
||||||
|
|
||||||
|
// test("No errors occured (exit code 0).", async () => {
|
||||||
|
// const TESTS_PATH = getTestJsonFilePath("env-flag-tests.json");
|
||||||
|
// const ENV_PATH = getTestJsonFilePath("env-flag-envs.json");
|
||||||
|
// const cmd = `node ./bin/hopp test ${TESTS_PATH} --env ${ENV_PATH}`;
|
||||||
|
// const { error } = await execAsync(cmd);
|
||||||
|
|
||||||
|
// expect(error).toBeNull();
|
||||||
|
// });
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { HoppCLIError } from "../../../types/errors";
|
import { HoppCLIError } from "../../../types/errors";
|
||||||
import { checkFilePath } from "../../../utils/checks";
|
import { checkFile } from "../../../utils/checks";
|
||||||
|
|
||||||
describe("checkFilePath", () => {
|
import "@relmify/jest-fp-ts";
|
||||||
|
|
||||||
|
describe("checkFile", () => {
|
||||||
test("File doesn't exists.", () => {
|
test("File doesn't exists.", () => {
|
||||||
return expect(
|
return expect(
|
||||||
checkFilePath("./src/samples/this-file-not-exists.json")()
|
checkFile("./src/samples/this-file-not-exists.json")()
|
||||||
).resolves.toSubsetEqualLeft(<HoppCLIError>{
|
).resolves.toSubsetEqualLeft(<HoppCLIError>{
|
||||||
code: "FILE_NOT_FOUND",
|
code: "FILE_NOT_FOUND",
|
||||||
});
|
});
|
||||||
@@ -12,15 +14,15 @@ describe("checkFilePath", () => {
|
|||||||
|
|
||||||
test("File not of JSON type.", () => {
|
test("File not of JSON type.", () => {
|
||||||
return expect(
|
return expect(
|
||||||
checkFilePath("./src/__tests__/samples/notjson.txt")()
|
checkFile("./src/__tests__/samples/notjson.txt")()
|
||||||
).resolves.toSubsetEqualLeft(<HoppCLIError>{
|
).resolves.toSubsetEqualLeft(<HoppCLIError>{
|
||||||
code: "FILE_NOT_JSON",
|
code: "INVALID_FILE_TYPE",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Existing JSON file.", () => {
|
test("Existing JSON file.", () => {
|
||||||
return expect(
|
return expect(
|
||||||
checkFilePath("./src/__tests__/samples/passes.json")()
|
checkFile("./src/__tests__/samples/passes.json")()
|
||||||
).resolves.toBeRight();
|
).resolves.toBeRight();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -37,6 +37,8 @@ const SAMPLE_RESOLVED_RESPONSE = <AxiosResponse>{
|
|||||||
headers: [],
|
headers: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const SAMPLE_ENVS = { global: [], selected: [] };
|
||||||
|
|
||||||
describe("collectionsRunner", () => {
|
describe("collectionsRunner", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
@@ -47,19 +49,24 @@ describe("collectionsRunner", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("Empty HoppCollection.", () => {
|
test("Empty HoppCollection.", () => {
|
||||||
return expect(collectionsRunner([])()).resolves.toStrictEqual([]);
|
return expect(
|
||||||
|
collectionsRunner({ collections: [], envs: SAMPLE_ENVS })()
|
||||||
|
).resolves.toStrictEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Empty requests and folders in collection.", () => {
|
test("Empty requests and folders in collection.", () => {
|
||||||
return expect(
|
return expect(
|
||||||
collectionsRunner([
|
collectionsRunner({
|
||||||
|
collections: [
|
||||||
{
|
{
|
||||||
v: 1,
|
v: 1,
|
||||||
name: "name",
|
name: "name",
|
||||||
folders: [],
|
folders: [],
|
||||||
requests: [],
|
requests: [],
|
||||||
},
|
},
|
||||||
])()
|
],
|
||||||
|
envs: SAMPLE_ENVS,
|
||||||
|
})()
|
||||||
).resolves.toMatchObject([]);
|
).resolves.toMatchObject([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -67,14 +74,17 @@ describe("collectionsRunner", () => {
|
|||||||
(axios as unknown as jest.Mock).mockResolvedValue(SAMPLE_RESOLVED_RESPONSE);
|
(axios as unknown as jest.Mock).mockResolvedValue(SAMPLE_RESOLVED_RESPONSE);
|
||||||
|
|
||||||
return expect(
|
return expect(
|
||||||
collectionsRunner([
|
collectionsRunner({
|
||||||
|
collections: [
|
||||||
{
|
{
|
||||||
v: 1,
|
v: 1,
|
||||||
name: "collection",
|
name: "collection",
|
||||||
folders: [],
|
folders: [],
|
||||||
requests: [SAMPLE_HOPP_REQUEST],
|
requests: [SAMPLE_HOPP_REQUEST],
|
||||||
},
|
},
|
||||||
])()
|
],
|
||||||
|
envs: SAMPLE_ENVS,
|
||||||
|
})()
|
||||||
).resolves.toMatchObject([
|
).resolves.toMatchObject([
|
||||||
{
|
{
|
||||||
path: "collection/request",
|
path: "collection/request",
|
||||||
@@ -89,7 +99,8 @@ describe("collectionsRunner", () => {
|
|||||||
(axios as unknown as jest.Mock).mockResolvedValue(SAMPLE_RESOLVED_RESPONSE);
|
(axios as unknown as jest.Mock).mockResolvedValue(SAMPLE_RESOLVED_RESPONSE);
|
||||||
|
|
||||||
return expect(
|
return expect(
|
||||||
collectionsRunner([
|
collectionsRunner({
|
||||||
|
collections: [
|
||||||
{
|
{
|
||||||
v: 1,
|
v: 1,
|
||||||
name: "collection",
|
name: "collection",
|
||||||
@@ -103,7 +114,9 @@ describe("collectionsRunner", () => {
|
|||||||
],
|
],
|
||||||
requests: [],
|
requests: [],
|
||||||
},
|
},
|
||||||
])()
|
],
|
||||||
|
envs: SAMPLE_ENVS,
|
||||||
|
})()
|
||||||
).resolves.toMatchObject([
|
).resolves.toMatchObject([
|
||||||
{
|
{
|
||||||
path: "collection/folder/request",
|
path: "collection/folder/request",
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { Environment } from "@hoppscotch/data";
|
import { Environment } from "@hoppscotch/data";
|
||||||
import { getEffectiveFinalMetaData } from "../../../utils/getters";
|
import { getEffectiveFinalMetaData } from "../../../utils/getters";
|
||||||
|
|
||||||
|
import "@relmify/jest-fp-ts";
|
||||||
|
|
||||||
const DEFAULT_ENV = <Environment>{
|
const DEFAULT_ENV = <Environment>{
|
||||||
name: "name",
|
name: "name",
|
||||||
variables: [{ key: "PARAM", value: "parsed_param" }],
|
variables: [{ key: "PARAM", value: "parsed_param" }],
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { HoppCLIError } from "../../../types/errors";
|
import { HoppCLIError } from "../../../types/errors";
|
||||||
import { parseCollectionData } from "../../../utils/mutators";
|
import { parseCollectionData } from "../../../utils/mutators";
|
||||||
|
|
||||||
|
import "@relmify/jest-fp-ts";
|
||||||
|
|
||||||
describe("parseCollectionData", () => {
|
describe("parseCollectionData", () => {
|
||||||
test("Reading non-existing file.", () => {
|
test("Reading non-existing file.", () => {
|
||||||
return expect(
|
return expect(
|
||||||
parseCollectionData("./src/__tests__/samples/notexist.txt")()
|
parseCollectionData("./src/__tests__/samples/notexist.json")()
|
||||||
).resolves.toSubsetEqualLeft(<HoppCLIError>{
|
).resolves.toSubsetEqualLeft(<HoppCLIError>{
|
||||||
code: "UNKNOWN_ERROR",
|
code: "FILE_NOT_FOUND",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { EffectiveHoppRESTRequest } from "../../../interfaces/request";
|
|||||||
import { HoppCLIError } from "../../../types/errors";
|
import { HoppCLIError } from "../../../types/errors";
|
||||||
import { getEffectiveRESTRequest } from "../../../utils/pre-request";
|
import { getEffectiveRESTRequest } from "../../../utils/pre-request";
|
||||||
|
|
||||||
|
import "@relmify/jest-fp-ts";
|
||||||
|
|
||||||
const DEFAULT_ENV = <Environment>{
|
const DEFAULT_ENV = <Environment>{
|
||||||
name: "name",
|
name: "name",
|
||||||
variables: [
|
variables: [
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"URL": "https://echo.hoppscotch.io",
|
||||||
|
"HOST": "echo.hoppscotch.io",
|
||||||
|
"X-COUNTRY": "IN",
|
||||||
|
"BODY_VALUE": "body_value",
|
||||||
|
"BODY_KEY": "body_key"
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"v": 1,
|
||||||
|
"name": "env-flag-tests",
|
||||||
|
"folders": [],
|
||||||
|
"requests": [
|
||||||
|
{
|
||||||
|
"v": "1",
|
||||||
|
"endpoint": "<<URL>>",
|
||||||
|
"name": "test1",
|
||||||
|
"params": [],
|
||||||
|
"headers": [],
|
||||||
|
"method": "POST",
|
||||||
|
"auth": { "authType": "none", "authActive": true },
|
||||||
|
"preRequestScript": "",
|
||||||
|
"testScript": "const HOST = pw.env.get(\"HOST\");\nconst UNSET_ENV = pw.env.get(\"UNSET_ENV\");\nconst EXPECTED_URL = \"https://echo.hoppscotch.io\";\nconst URL = pw.env.get(\"URL\");\nconst X_COUNTRY = pw.env.get(\"X-COUNTRY\");\nconst BODY_VALUE = pw.env.get(\"BODY_VALUE\");\n\n// Check JSON response property\npw.test(\"Check headers properties.\", ()=> {\n pw.expect(pw.response.body.headers.host).toBe(HOST);\n\t pw.expect(pw.response.body.headers[\"x-country\"]).toBe(X_COUNTRY); \n});\n\npw.test(\"Check data properties.\", () => {\n\tconst DATA = pw.response.body.data;\n \n pw.expect(DATA).toBeType(\"string\");\n pw.expect(JSON.parse(DATA).body_key).toBe(BODY_VALUE);\n});\n\npw.test(\"Check request URL.\", () => {\n pw.expect(URL).toBe(EXPECTED_URL);\n})\n\npw.test(\"Check unset ENV.\", () => {\n pw.expect(UNSET_ENV).toBeType(\"undefined\");\n})",
|
||||||
|
"body": {
|
||||||
|
"contentType": "application/json",
|
||||||
|
"body": "{\n \"<<BODY_KEY>>\":\"<<BODY_VALUE>>\"\n}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -6,14 +6,15 @@ import {
|
|||||||
collectionsRunnerResult,
|
collectionsRunnerResult,
|
||||||
} from "../utils/collections";
|
} from "../utils/collections";
|
||||||
import { handleError } from "../handlers/error";
|
import { handleError } from "../handlers/error";
|
||||||
import { checkFilePath } from "../utils/checks";
|
|
||||||
import { parseCollectionData } from "../utils/mutators";
|
import { parseCollectionData } from "../utils/mutators";
|
||||||
|
import { parseEnvsData } from "../options/test/env";
|
||||||
|
import { TestCmdOptions } from "../types/commands";
|
||||||
|
|
||||||
export const test = (path: string) => async () => {
|
export const test = (path: string, options: TestCmdOptions) => async () => {
|
||||||
await pipe(
|
await pipe(
|
||||||
path,
|
TE.Do,
|
||||||
checkFilePath,
|
TE.bind("envs", () => parseEnvsData(options.env)),
|
||||||
TE.chain(parseCollectionData),
|
TE.bind("collections", () => parseCollectionData(path)),
|
||||||
TE.chainTaskK(collectionsRunner),
|
TE.chainTaskK(collectionsRunner),
|
||||||
TE.chainW(flow(collectionsRunnerResult, collectionsRunnerExit, TE.of)),
|
TE.chainW(flow(collectionsRunnerResult, collectionsRunnerExit, TE.of)),
|
||||||
TE.mapLeft((e) => {
|
TE.mapLeft((e) => {
|
||||||
|
|||||||
@@ -48,9 +48,7 @@ export const handleError = <T extends HoppErrorCode>(error: HoppError<T>) => {
|
|||||||
case "UNKNOWN_COMMAND":
|
case "UNKNOWN_COMMAND":
|
||||||
ERROR_MSG = `Unavailable command: ${error.command}`;
|
ERROR_MSG = `Unavailable command: ${error.command}`;
|
||||||
break;
|
break;
|
||||||
case "FILE_NOT_JSON":
|
case "MALFORMED_ENV_FILE":
|
||||||
ERROR_MSG = `Please check file type: ${error.path}`;
|
|
||||||
break;
|
|
||||||
case "MALFORMED_COLLECTION":
|
case "MALFORMED_COLLECTION":
|
||||||
ERROR_MSG = `${error.path}\n${parseErrorData(error.data)}`;
|
ERROR_MSG = `${error.path}\n${parseErrorData(error.data)}`;
|
||||||
break;
|
break;
|
||||||
@@ -60,6 +58,9 @@ export const handleError = <T extends HoppErrorCode>(error: HoppError<T>) => {
|
|||||||
case "PARSING_ERROR":
|
case "PARSING_ERROR":
|
||||||
ERROR_MSG = `Unable to parse -\n${error.data}`;
|
ERROR_MSG = `Unable to parse -\n${error.data}`;
|
||||||
break;
|
break;
|
||||||
|
case "INVALID_FILE_TYPE":
|
||||||
|
ERROR_MSG = `Please provide file of extension type: ${error.data}`;
|
||||||
|
break;
|
||||||
case "REQUEST_ERROR":
|
case "REQUEST_ERROR":
|
||||||
case "TEST_SCRIPT_ERROR":
|
case "TEST_SCRIPT_ERROR":
|
||||||
case "PRE_REQUEST_SCRIPT_ERROR":
|
case "PRE_REQUEST_SCRIPT_ERROR":
|
||||||
|
|||||||
@@ -5,14 +5,16 @@ import { version } from "../package.json";
|
|||||||
import { test } from "./commands/test";
|
import { test } from "./commands/test";
|
||||||
import { handleError } from "./handlers/error";
|
import { handleError } from "./handlers/error";
|
||||||
|
|
||||||
const accent = chalk.greenBright
|
const accent = chalk.greenBright;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* * Program Default Configuration
|
* * Program Default Configuration
|
||||||
*/
|
*/
|
||||||
const CLI_BEFORE_ALL_TXT = `hopp: The ${accent(
|
const CLI_BEFORE_ALL_TXT = `hopp: The ${accent(
|
||||||
"Hoppscotch"
|
"Hoppscotch"
|
||||||
)} CLI - Version ${version} (${accent("https://hoppscotch.io")}) ${chalk.black.bold.bgYellowBright(" ALPHA ")} \n`;
|
)} CLI - Version ${version} (${accent(
|
||||||
|
"https://hoppscotch.io"
|
||||||
|
)}) ${chalk.black.bold.bgYellowBright(" ALPHA ")} \n`;
|
||||||
|
|
||||||
const CLI_AFTER_ALL_TXT = `\nFor more help, head on to ${accent(
|
const CLI_AFTER_ALL_TXT = `\nFor more help, head on to ${accent(
|
||||||
"https://docs.hoppscotch.io/cli"
|
"https://docs.hoppscotch.io/cli"
|
||||||
@@ -44,14 +46,18 @@ program.exitOverride().configureOutput({
|
|||||||
program
|
program
|
||||||
.command("test")
|
.command("test")
|
||||||
.argument(
|
.argument(
|
||||||
"[file]",
|
"<file_path>",
|
||||||
"path to a hoppscotch collection.json file for CI testing"
|
"path to a hoppscotch collection.json file for CI testing"
|
||||||
)
|
)
|
||||||
|
.option("-e, --env <file_path>", "path to an environment variables json file")
|
||||||
.allowExcessArguments(false)
|
.allowExcessArguments(false)
|
||||||
.allowUnknownOption(false)
|
.allowUnknownOption(false)
|
||||||
.description("running hoppscotch collection.json file")
|
.description("running hoppscotch collection.json file")
|
||||||
.addHelpText("after", `\nFor help, head on to ${accent("https://docs.hoppscotch.io/cli#test")}`)
|
.addHelpText(
|
||||||
.action(async (path) => await test(path)());
|
"after",
|
||||||
|
`\nFor help, head on to ${accent("https://docs.hoppscotch.io/cli#test")}`
|
||||||
|
)
|
||||||
|
.action(async (path, options) => await test(path, options)());
|
||||||
|
|
||||||
export const cli = async (args: string[]) => {
|
export const cli = async (args: string[]) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
64
packages/hoppscotch-cli/src/options/test/env.ts
Normal file
64
packages/hoppscotch-cli/src/options/test/env.ts
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import fs from "fs/promises";
|
||||||
|
import { pipe } from "fp-ts/function";
|
||||||
|
import * as TE from "fp-ts/TaskEither";
|
||||||
|
import * as E from "fp-ts/Either";
|
||||||
|
import * as J from "fp-ts/Json";
|
||||||
|
import * as A from "fp-ts/Array";
|
||||||
|
import * as S from "fp-ts/string";
|
||||||
|
import isArray from "lodash/isArray";
|
||||||
|
import { HoppCLIError, error } from "../../types/errors";
|
||||||
|
import { HoppEnvs, HoppEnvPair } from "../../types/request";
|
||||||
|
import { checkFile } from "../../utils/checks";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses env json file for given path and validates the parsed env json object.
|
||||||
|
* @param path Path of env.json file to be parsed.
|
||||||
|
* @returns For successful parsing we get HoppEnvs object.
|
||||||
|
*/
|
||||||
|
export const parseEnvsData = (
|
||||||
|
path: unknown
|
||||||
|
): TE.TaskEither<HoppCLIError, HoppEnvs> =>
|
||||||
|
!S.isString(path)
|
||||||
|
? TE.right({ global: [], selected: [] })
|
||||||
|
: pipe(
|
||||||
|
// Checking if the env.json file exists or not.
|
||||||
|
checkFile(path),
|
||||||
|
|
||||||
|
// Trying to read given env json file path.
|
||||||
|
TE.chainW((checkedPath) =>
|
||||||
|
TE.tryCatch(
|
||||||
|
() => fs.readFile(checkedPath),
|
||||||
|
(reason) =>
|
||||||
|
error({ code: "UNKNOWN_ERROR", data: E.toError(reason) })
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
// Trying to JSON parse the read file data and mapping the entries to HoppEnvPairs.
|
||||||
|
TE.chainEitherKW((data) =>
|
||||||
|
pipe(
|
||||||
|
data.toString(),
|
||||||
|
J.parse,
|
||||||
|
E.map((jsonData) =>
|
||||||
|
jsonData && typeof jsonData === "object" && !isArray(jsonData)
|
||||||
|
? pipe(
|
||||||
|
jsonData,
|
||||||
|
Object.entries,
|
||||||
|
A.map(
|
||||||
|
([key, value]) =>
|
||||||
|
<HoppEnvPair>{
|
||||||
|
key,
|
||||||
|
value: S.isString(value)
|
||||||
|
? value
|
||||||
|
: JSON.stringify(value),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
: []
|
||||||
|
),
|
||||||
|
E.map((envPairs) => <HoppEnvs>{ global: [], selected: envPairs }),
|
||||||
|
E.mapLeft((e) =>
|
||||||
|
error({ code: "MALFORMED_ENV_FILE", path, data: E.toError(e) })
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
9
packages/hoppscotch-cli/src/types/collections.ts
Normal file
9
packages/hoppscotch-cli/src/types/collections.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data";
|
||||||
|
import { HoppEnvs } from "./request";
|
||||||
|
|
||||||
|
export type CollectionRunnerParam = {
|
||||||
|
collections: HoppCollection<HoppRESTRequest>[];
|
||||||
|
envs: HoppEnvs;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type HoppCollectionFileExt = "json";
|
||||||
5
packages/hoppscotch-cli/src/types/commands.ts
Normal file
5
packages/hoppscotch-cli/src/types/commands.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export type TestCmdOptions = {
|
||||||
|
env: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type HoppEnvFileExt = "json";
|
||||||
@@ -15,7 +15,6 @@ type HoppErrors = {
|
|||||||
FILE_NOT_FOUND: HoppErrorPath;
|
FILE_NOT_FOUND: HoppErrorPath;
|
||||||
UNKNOWN_COMMAND: HoppErrorCmd;
|
UNKNOWN_COMMAND: HoppErrorCmd;
|
||||||
MALFORMED_COLLECTION: HoppErrorPath & HoppErrorData;
|
MALFORMED_COLLECTION: HoppErrorPath & HoppErrorData;
|
||||||
FILE_NOT_JSON: HoppErrorPath;
|
|
||||||
NO_FILE_PATH: {};
|
NO_FILE_PATH: {};
|
||||||
PRE_REQUEST_SCRIPT_ERROR: HoppErrorData;
|
PRE_REQUEST_SCRIPT_ERROR: HoppErrorData;
|
||||||
PARSING_ERROR: HoppErrorData;
|
PARSING_ERROR: HoppErrorData;
|
||||||
@@ -24,6 +23,8 @@ type HoppErrors = {
|
|||||||
SYNTAX_ERROR: HoppErrorData;
|
SYNTAX_ERROR: HoppErrorData;
|
||||||
REQUEST_ERROR: HoppErrorData;
|
REQUEST_ERROR: HoppErrorData;
|
||||||
INVALID_ARGUMENT: HoppErrorData;
|
INVALID_ARGUMENT: HoppErrorData;
|
||||||
|
MALFORMED_ENV_FILE: HoppErrorPath & HoppErrorData;
|
||||||
|
INVALID_FILE_TYPE: HoppErrorData;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type HoppErrorCode = keyof HoppErrors;
|
export type HoppErrorCode = keyof HoppErrors;
|
||||||
|
|||||||
@@ -7,15 +7,11 @@ export type FormDataEntry = {
|
|||||||
value: string | Blob;
|
value: string | Blob;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type HoppEnvPair = { key: string; value: string };
|
||||||
|
|
||||||
export type HoppEnvs = {
|
export type HoppEnvs = {
|
||||||
global: {
|
global: HoppEnvPair[];
|
||||||
key: string;
|
selected: HoppEnvPair[];
|
||||||
value: string;
|
|
||||||
}[];
|
|
||||||
selected: {
|
|
||||||
key: string;
|
|
||||||
value: string;
|
|
||||||
}[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CollectionStack = {
|
export type CollectionStack = {
|
||||||
|
|||||||
@@ -9,8 +9,12 @@ import {
|
|||||||
import * as A from "fp-ts/Array";
|
import * as A from "fp-ts/Array";
|
||||||
import * as S from "fp-ts/string";
|
import * as S from "fp-ts/string";
|
||||||
import * as TE from "fp-ts/TaskEither";
|
import * as TE from "fp-ts/TaskEither";
|
||||||
import { error, HoppCLIError, HoppErrnoException } from "../types/errors";
|
import * as E from "fp-ts/Either";
|
||||||
|
import curryRight from "lodash/curryRight";
|
||||||
import { CommanderError } from "commander";
|
import { CommanderError } from "commander";
|
||||||
|
import { error, HoppCLIError, HoppErrnoException } from "../types/errors";
|
||||||
|
import { HoppCollectionFileExt } from "../types/collections";
|
||||||
|
import { HoppEnvFileExt } from "../types/commands";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether an object has a property with given name.
|
* Determines whether an object has a property with given name.
|
||||||
@@ -68,42 +72,56 @@ export const isRESTCollection = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the given file path exists and is of JSON type.
|
* Checks if the file path matches the requried file type with of required extension.
|
||||||
|
* @param path The input file path to check.
|
||||||
|
* @param extension The required extension for input file path.
|
||||||
|
* @returns Absolute path for valid file extension OR HoppCLIError in case of error.
|
||||||
|
*/
|
||||||
|
export const checkFileExt = curryRight(
|
||||||
|
(
|
||||||
|
path: unknown,
|
||||||
|
extension: HoppCollectionFileExt | HoppEnvFileExt
|
||||||
|
): E.Either<HoppCLIError, string> =>
|
||||||
|
pipe(
|
||||||
|
path,
|
||||||
|
E.fromPredicate(S.isString, (_) => error({ code: "NO_FILE_PATH" })),
|
||||||
|
E.chainW(
|
||||||
|
E.fromPredicate(S.endsWith(`.${extension}`), (_) =>
|
||||||
|
error({ code: "INVALID_FILE_TYPE", data: extension })
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the given file path exists and is of given type.
|
||||||
* @param path The input file path to check.
|
* @param path The input file path to check.
|
||||||
* @returns Absolute path for valid file path OR HoppCLIError in case of error.
|
* @returns Absolute path for valid file path OR HoppCLIError in case of error.
|
||||||
*/
|
*/
|
||||||
export const checkFilePath = (
|
export const checkFile = (path: unknown): TE.TaskEither<HoppCLIError, string> =>
|
||||||
path: string
|
|
||||||
): TE.TaskEither<HoppCLIError, string> =>
|
|
||||||
pipe(
|
pipe(
|
||||||
path,
|
path,
|
||||||
|
|
||||||
/**
|
// Checking if path is string.
|
||||||
* Check the path type and returns string if passes else HoppCLIError.
|
|
||||||
*/
|
|
||||||
TE.fromPredicate(S.isString, () => error({ code: "NO_FILE_PATH" })),
|
TE.fromPredicate(S.isString, () => error({ code: "NO_FILE_PATH" })),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* After checking file path, we map file path to absolute path and check
|
||||||
|
* if file is of given extension type.
|
||||||
|
*/
|
||||||
|
TE.map(join),
|
||||||
|
TE.chainEitherK(checkFileExt("json")),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trying to access given file path.
|
* Trying to access given file path.
|
||||||
* If successfully accessed, we return the path from predicate step.
|
* If successfully accessed, we return the path from predicate step.
|
||||||
* Else return HoppCLIError with code FILE_NOT_FOUND.
|
* Else return HoppCLIError with code FILE_NOT_FOUND.
|
||||||
*/
|
*/
|
||||||
TE.chainFirstW(
|
TE.chainFirstW((checkedPath) =>
|
||||||
TE.tryCatchK(
|
TE.tryCatchK(
|
||||||
() => pipe(path, join, fs.access),
|
() => fs.access(checkedPath),
|
||||||
() => error({ code: "FILE_NOT_FOUND", path: path })
|
() => error({ code: "FILE_NOT_FOUND", path: checkedPath })
|
||||||
)
|
)()
|
||||||
),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On successfully accessing given file path, we map file path to
|
|
||||||
* absolute path and return abs file path if file is JSON type.
|
|
||||||
*/
|
|
||||||
TE.map(join),
|
|
||||||
TE.chainW(
|
|
||||||
TE.fromPredicate(S.endsWith(".json"), (absPath) =>
|
|
||||||
error({ code: "FILE_NOT_JSON", path: absPath })
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -27,21 +27,24 @@ import {
|
|||||||
import { getTestMetrics } from "./test";
|
import { getTestMetrics } from "./test";
|
||||||
import { DEFAULT_DURATION_PRECISION } from "./constants";
|
import { DEFAULT_DURATION_PRECISION } from "./constants";
|
||||||
import { getPreRequestMetrics } from "./pre-request";
|
import { getPreRequestMetrics } from "./pre-request";
|
||||||
|
import { CollectionRunnerParam } from "../types/collections";
|
||||||
|
|
||||||
const { WARN, FAIL } = exceptionColors;
|
const { WARN, FAIL } = exceptionColors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes each requests within collections to prints details of subsequent requests,
|
* Processes each requests within collections to prints details of subsequent requests,
|
||||||
* tests and to display complete errors-report, failed-tests-report and test-metrics.
|
* tests and to display complete errors-report, failed-tests-report and test-metrics.
|
||||||
* @param collections Array of hopp-collection with hopp-requests to be processed.
|
* @param param Data of hopp-collection with hopp-requests, envs to be processed.
|
||||||
* @returns List of report for each processed request.
|
* @returns List of report for each processed request.
|
||||||
*/
|
*/
|
||||||
export const collectionsRunner =
|
export const collectionsRunner =
|
||||||
(collections: HoppCollection<HoppRESTRequest>[]): T.Task<RequestReport[]> =>
|
(param: CollectionRunnerParam): T.Task<RequestReport[]> =>
|
||||||
async () => {
|
async () => {
|
||||||
const envs: HoppEnvs = { global: [], selected: [] };
|
const envs: HoppEnvs = param.envs;
|
||||||
const requestsReport: RequestReport[] = [];
|
const requestsReport: RequestReport[] = [];
|
||||||
const collectionStack: CollectionStack[] = getCollectionStack(collections);
|
const collectionStack: CollectionStack[] = getCollectionStack(
|
||||||
|
param.collections
|
||||||
|
);
|
||||||
|
|
||||||
while (collectionStack.length) {
|
while (collectionStack.length) {
|
||||||
// Pop out top-most collection from stack to be processed.
|
// Pop out top-most collection from stack to be processed.
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import * as J from "fp-ts/Json";
|
|||||||
import { pipe } from "fp-ts/function";
|
import { pipe } from "fp-ts/function";
|
||||||
import { FormDataEntry } from "../types/request";
|
import { FormDataEntry } from "../types/request";
|
||||||
import { error, HoppCLIError } from "../types/errors";
|
import { error, HoppCLIError } from "../types/errors";
|
||||||
import { isRESTCollection, isHoppErrnoException } from "./checks";
|
import { isRESTCollection, isHoppErrnoException, checkFile } from "./checks";
|
||||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data";
|
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -49,10 +49,17 @@ export const parseCollectionData = (
|
|||||||
path: string
|
path: string
|
||||||
): TE.TaskEither<HoppCLIError, HoppCollection<HoppRESTRequest>[]> =>
|
): TE.TaskEither<HoppCLIError, HoppCollection<HoppRESTRequest>[]> =>
|
||||||
pipe(
|
pipe(
|
||||||
|
TE.of(path),
|
||||||
|
|
||||||
|
// Checking if given file path exists or not.
|
||||||
|
TE.chain(checkFile),
|
||||||
|
|
||||||
// Trying to read give collection json path.
|
// Trying to read give collection json path.
|
||||||
|
TE.chainW((checkedPath) =>
|
||||||
TE.tryCatch(
|
TE.tryCatch(
|
||||||
() => pipe(path, fs.readFile),
|
() => fs.readFile(checkedPath),
|
||||||
(reason) => error({ code: "UNKNOWN_ERROR", data: E.toError(reason) })
|
(reason) => error({ code: "UNKNOWN_ERROR", data: E.toError(reason) })
|
||||||
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
// Checking if parsed file data is array.
|
// Checking if parsed file data is array.
|
||||||
|
|||||||
811
pnpm-lock.yaml
generated
811
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user