Compare commits
36 Commits
fix/genera
...
feat/condi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58782fd8d8 | ||
|
|
42cb728577 | ||
|
|
b9f36d04a8 | ||
|
|
1704cc2bad | ||
|
|
ab9775d55f | ||
|
|
88f6a4ae26 | ||
|
|
af7ff9dc17 | ||
|
|
610538ca02 | ||
|
|
8970ff5c68 | ||
|
|
d1a564d5b8 | ||
|
|
cf29c29d16 | ||
|
|
8bb1d19c07 | ||
|
|
c3d03d7162 | ||
|
|
a72b1feda5 | ||
|
|
bdab2bb2d0 | ||
|
|
e50b3ddcc4 | ||
|
|
8d59a69f48 | ||
|
|
c1efa381f0 | ||
|
|
d090585685 | ||
|
|
7b590fa57d | ||
|
|
83536ad715 | ||
|
|
197b48182f | ||
|
|
48356cb589 | ||
|
|
29171d1b6f | ||
|
|
e869d49e16 | ||
|
|
6496bea846 | ||
|
|
39842559b5 | ||
|
|
51efb35aa6 | ||
|
|
9402bb9285 | ||
|
|
5a516f7242 | ||
|
|
3b217d78e7 | ||
|
|
8e153b38dc | ||
|
|
6f38bfb148 | ||
|
|
82b6e08d68 | ||
|
|
25177bd635 | ||
|
|
6928eb7992 |
1
.dockerignore
Normal file
1
.dockerignore
Normal file
@@ -0,0 +1 @@
|
||||
*/**/node_modules
|
||||
@@ -13,6 +13,7 @@ SESSION_SECRET='add some secret here'
|
||||
# Hoppscotch App Domain Config
|
||||
REDIRECT_URL="http://localhost:3000"
|
||||
WHITELISTED_ORIGINS = "http://localhost:3170,http://localhost:3000,http://localhost:3100"
|
||||
ALLOWED_AUTH_PROVIDERS = GOOGLE,GITHUB,MICROSOFT,EMAIL
|
||||
|
||||
# Google Auth Config
|
||||
GOOGLE_CLIENT_ID="************************************************"
|
||||
|
||||
4
.github/workflows/tests.yml
vendored
4
.github/workflows/tests.yml
vendored
@@ -2,9 +2,9 @@ name: Node.js CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, staging]
|
||||
branches: [main, staging, "release/**"]
|
||||
pull_request:
|
||||
branches: [main, staging]
|
||||
branches: [main, staging, "release/**"]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
|
||||
@@ -19,10 +19,12 @@ services:
|
||||
- DATABASE_URL=postgresql://postgres:testpass@hoppscotch-db:5432/hoppscotch?connect_timeout=300
|
||||
- PORT=3000
|
||||
volumes:
|
||||
- ./packages/hoppscotch-backend/:/usr/src/app
|
||||
# Uncomment the line below when modifying code. Only applicable when using the "dev" target.
|
||||
# - ./packages/hoppscotch-backend/:/usr/src/app
|
||||
- /usr/src/app/node_modules/
|
||||
depends_on:
|
||||
- hoppscotch-db
|
||||
hoppscotch-db:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "3170:3000"
|
||||
|
||||
@@ -60,12 +62,20 @@ services:
|
||||
# you are using an external postgres instance
|
||||
# This will be exposed at port 5432
|
||||
hoppscotch-db:
|
||||
image: postgres
|
||||
image: postgres:15
|
||||
ports:
|
||||
- "5432:5432"
|
||||
user: postgres
|
||||
environment:
|
||||
# The default user defined by the docker image
|
||||
POSTGRES_USER: postgres
|
||||
# NOTE: Please UPDATE THIS PASSWORD!
|
||||
POSTGRES_PASSWORD: testpass
|
||||
POSTGRES_DB: hoppscotch
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "sh -c 'pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}'"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hoppscotch-backend",
|
||||
"version": "2023.4.7",
|
||||
"version": "2023.4.8",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
|
||||
@@ -411,6 +411,23 @@ export class AdminResolver {
|
||||
return deletedTeam.right;
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean, {
|
||||
description: 'Revoke a team Invite by Invite ID',
|
||||
})
|
||||
@UseGuards(GqlAuthGuard, GqlAdminGuard)
|
||||
async revokeTeamInviteByAdmin(
|
||||
@Args({
|
||||
name: 'inviteID',
|
||||
description: 'Team Invite ID',
|
||||
type: () => ID,
|
||||
})
|
||||
inviteID: string,
|
||||
): Promise<boolean> {
|
||||
const invite = await this.adminService.revokeTeamInviteByID(inviteID);
|
||||
if (E.isLeft(invite)) throwErr(invite.left);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Subscriptions */
|
||||
|
||||
@Subscription(() => InvitedUser, {
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
INVALID_EMAIL,
|
||||
ONLY_ONE_ADMIN_ACCOUNT,
|
||||
TEAM_INVITE_ALREADY_MEMBER,
|
||||
TEAM_INVITE_NO_INVITE_FOUND,
|
||||
USER_ALREADY_INVITED,
|
||||
USER_IS_ADMIN,
|
||||
USER_NOT_FOUND,
|
||||
@@ -416,4 +417,19 @@ export class AdminService {
|
||||
if (E.isLeft(team)) return E.left(team.left);
|
||||
return E.right(team.right);
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke a team invite by ID
|
||||
* @param inviteID Team Invite ID
|
||||
* @returns an Either of boolean or error
|
||||
*/
|
||||
async revokeTeamInviteByID(inviteID: string) {
|
||||
const teamInvite = await this.teamInvitationService.revokeInvitation(
|
||||
inviteID,
|
||||
);
|
||||
|
||||
if (E.isLeft(teamInvite)) return E.left(teamInvite.left);
|
||||
|
||||
return E.right(teamInvite.right);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
InternalServerErrorException,
|
||||
Post,
|
||||
Query,
|
||||
Req,
|
||||
Request,
|
||||
Res,
|
||||
UseGuards,
|
||||
@@ -19,12 +19,18 @@ import { JwtAuthGuard } from './guards/jwt-auth.guard';
|
||||
import { GqlUser } from 'src/decorators/gql-user.decorator';
|
||||
import { AuthUser } from 'src/types/AuthUser';
|
||||
import { RTCookie } from 'src/decorators/rt-cookie.decorator';
|
||||
import { authCookieHandler, throwHTTPErr } from './helper';
|
||||
import {
|
||||
AuthProvider,
|
||||
authCookieHandler,
|
||||
authProviderCheck,
|
||||
throwHTTPErr,
|
||||
} from './helper';
|
||||
import { GoogleSSOGuard } from './guards/google-sso.guard';
|
||||
import { GithubSSOGuard } from './guards/github-sso.guard';
|
||||
import { MicrosoftSSOGuard } from './guards/microsoft-sso-.guard';
|
||||
import { ThrottlerBehindProxyGuard } from 'src/guards/throttler-behind-proxy.guard';
|
||||
import { SkipThrottle } from '@nestjs/throttler';
|
||||
import { AUTH_PROVIDER_NOT_SPECIFIED } from 'src/errors';
|
||||
|
||||
@UseGuards(ThrottlerBehindProxyGuard)
|
||||
@Controller({ path: 'auth', version: '1' })
|
||||
@@ -39,6 +45,9 @@ export class AuthController {
|
||||
@Body() authData: SignInMagicDto,
|
||||
@Query('origin') origin: string,
|
||||
) {
|
||||
if (!authProviderCheck(AuthProvider.EMAIL))
|
||||
throwHTTPErr({ message: AUTH_PROVIDER_NOT_SPECIFIED, statusCode: 404 });
|
||||
|
||||
const deviceIdToken = await this.authService.signInMagicLink(
|
||||
authData.email,
|
||||
origin,
|
||||
|
||||
@@ -11,6 +11,7 @@ import { RTJwtStrategy } from './strategies/rt-jwt.strategy';
|
||||
import { GoogleStrategy } from './strategies/google.strategy';
|
||||
import { GithubStrategy } from './strategies/github.strategy';
|
||||
import { MicrosoftStrategy } from './strategies/microsoft.strategy';
|
||||
import { AuthProvider, authProviderCheck } from './helper';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -26,9 +27,9 @@ import { MicrosoftStrategy } from './strategies/microsoft.strategy';
|
||||
AuthService,
|
||||
JwtStrategy,
|
||||
RTJwtStrategy,
|
||||
GoogleStrategy,
|
||||
GithubStrategy,
|
||||
MicrosoftStrategy,
|
||||
...(authProviderCheck(AuthProvider.GOOGLE) ? [GoogleStrategy] : []),
|
||||
...(authProviderCheck(AuthProvider.GITHUB) ? [GithubStrategy] : []),
|
||||
...(authProviderCheck(AuthProvider.MICROSOFT) ? [MicrosoftStrategy] : []),
|
||||
],
|
||||
controllers: [AuthController],
|
||||
})
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
import { ExecutionContext, Injectable } from '@nestjs/common';
|
||||
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { AuthProvider, authProviderCheck, throwHTTPErr } from '../helper';
|
||||
import { Observable } from 'rxjs';
|
||||
import { AUTH_PROVIDER_NOT_SPECIFIED } from 'src/errors';
|
||||
|
||||
@Injectable()
|
||||
export class GithubSSOGuard extends AuthGuard('github') {
|
||||
export class GithubSSOGuard extends AuthGuard('github') implements CanActivate {
|
||||
canActivate(
|
||||
context: ExecutionContext,
|
||||
): boolean | Promise<boolean> | Observable<boolean> {
|
||||
if (!authProviderCheck(AuthProvider.GITHUB))
|
||||
throwHTTPErr({ message: AUTH_PROVIDER_NOT_SPECIFIED, statusCode: 404 });
|
||||
|
||||
return super.canActivate(context);
|
||||
}
|
||||
|
||||
getAuthenticateOptions(context: ExecutionContext) {
|
||||
const req = context.switchToHttp().getRequest();
|
||||
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
import { ExecutionContext, Injectable } from '@nestjs/common';
|
||||
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { AuthProvider, authProviderCheck, throwHTTPErr } from '../helper';
|
||||
import { Observable } from 'rxjs';
|
||||
import { AUTH_PROVIDER_NOT_SPECIFIED } from 'src/errors';
|
||||
|
||||
@Injectable()
|
||||
export class GoogleSSOGuard extends AuthGuard('google') {
|
||||
export class GoogleSSOGuard extends AuthGuard('google') implements CanActivate {
|
||||
canActivate(
|
||||
context: ExecutionContext,
|
||||
): boolean | Promise<boolean> | Observable<boolean> {
|
||||
if (!authProviderCheck(AuthProvider.GOOGLE))
|
||||
throwHTTPErr({ message: AUTH_PROVIDER_NOT_SPECIFIED, statusCode: 404 });
|
||||
|
||||
return super.canActivate(context);
|
||||
}
|
||||
|
||||
getAuthenticateOptions(context: ExecutionContext) {
|
||||
const req = context.switchToHttp().getRequest();
|
||||
|
||||
|
||||
@@ -1,8 +1,26 @@
|
||||
import { ExecutionContext, Injectable } from '@nestjs/common';
|
||||
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { AuthProvider, authProviderCheck, throwHTTPErr } from '../helper';
|
||||
import { Observable } from 'rxjs';
|
||||
import { AUTH_PROVIDER_NOT_SPECIFIED } from 'src/errors';
|
||||
|
||||
@Injectable()
|
||||
export class MicrosoftSSOGuard extends AuthGuard('microsoft') {
|
||||
export class MicrosoftSSOGuard
|
||||
extends AuthGuard('microsoft')
|
||||
implements CanActivate
|
||||
{
|
||||
canActivate(
|
||||
context: ExecutionContext,
|
||||
): boolean | Promise<boolean> | Observable<boolean> {
|
||||
if (!authProviderCheck(AuthProvider.MICROSOFT))
|
||||
throwHTTPErr({
|
||||
message: AUTH_PROVIDER_NOT_SPECIFIED,
|
||||
statusCode: 404,
|
||||
});
|
||||
|
||||
return super.canActivate(context);
|
||||
}
|
||||
|
||||
getAuthenticateOptions(context: ExecutionContext) {
|
||||
const req = context.switchToHttp().getRequest();
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { ForbiddenException, HttpException, HttpStatus } from '@nestjs/common';
|
||||
import { HttpException, HttpStatus } from '@nestjs/common';
|
||||
import { DateTime } from 'luxon';
|
||||
import { AuthError } from 'src/types/AuthError';
|
||||
import { AuthTokens } from 'src/types/AuthTokens';
|
||||
import { Response } from 'express';
|
||||
import * as cookie from 'cookie';
|
||||
import { COOKIES_NOT_FOUND } from 'src/errors';
|
||||
import { AUTH_PROVIDER_NOT_SPECIFIED, COOKIES_NOT_FOUND } from 'src/errors';
|
||||
import { throwErr } from 'src/utils';
|
||||
|
||||
enum AuthTokenType {
|
||||
ACCESS_TOKEN = 'access_token',
|
||||
@@ -16,6 +17,13 @@ export enum Origin {
|
||||
APP = 'app',
|
||||
}
|
||||
|
||||
export enum AuthProvider {
|
||||
GOOGLE = 'GOOGLE',
|
||||
GITHUB = 'GITHUB',
|
||||
MICROSOFT = 'MICROSOFT',
|
||||
EMAIL = 'EMAIL',
|
||||
}
|
||||
|
||||
/**
|
||||
* This function allows throw to be used as an expression
|
||||
* @param errMessage Message present in the error message
|
||||
@@ -97,3 +105,25 @@ export const subscriptionContextCookieParser = (rawCookies: string) => {
|
||||
refresh_token: cookies[AuthTokenType.REFRESH_TOKEN],
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Check to see if given auth provider is present in the ALLOWED_AUTH_PROVIDERS env variable
|
||||
*
|
||||
* @param provider Provider we want to check the presence of
|
||||
* @returns Boolean if provider specified is present or not
|
||||
*/
|
||||
export function authProviderCheck(provider: string) {
|
||||
if (!provider) {
|
||||
throwErr(AUTH_PROVIDER_NOT_SPECIFIED);
|
||||
}
|
||||
|
||||
const envVariables = process.env.ALLOWED_AUTH_PROVIDERS
|
||||
? process.env.ALLOWED_AUTH_PROVIDERS.split(',').map((provider) =>
|
||||
provider.trim().toUpperCase(),
|
||||
)
|
||||
: [];
|
||||
|
||||
if (!envVariables.includes(provider.toUpperCase())) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,30 @@ export const AUTH_FAIL = 'auth/fail';
|
||||
*/
|
||||
export const JSON_INVALID = 'json_invalid';
|
||||
|
||||
/**
|
||||
* Auth Provider not specified
|
||||
* (Auth)
|
||||
*/
|
||||
export const AUTH_PROVIDER_NOT_SPECIFIED = 'auth/provider_not_specified';
|
||||
|
||||
/**
|
||||
* Environment variable "ALLOWED_AUTH_PROVIDERS" is not present in .env file
|
||||
*/
|
||||
export const ENV_NOT_FOUND_KEY_AUTH_PROVIDERS =
|
||||
'"ALLOWED_AUTH_PROVIDERS" is not present in .env file';
|
||||
|
||||
/**
|
||||
* Environment variable "ALLOWED_AUTH_PROVIDERS" is empty in .env file
|
||||
*/
|
||||
export const ENV_EMPTY_AUTH_PROVIDERS =
|
||||
'"ALLOWED_AUTH_PROVIDERS" is empty in .env file';
|
||||
|
||||
/**
|
||||
* Environment variable "ALLOWED_AUTH_PROVIDERS" contains unsupported provider in .env file
|
||||
*/
|
||||
export const ENV_NOT_SUPPORT_AUTH_PROVIDERS =
|
||||
'"ALLOWED_AUTH_PROVIDERS" contains an unsupported auth provider in .env file';
|
||||
|
||||
/**
|
||||
* Tried to delete a user data document from fb firestore but failed.
|
||||
* (FirebaseService)
|
||||
|
||||
@@ -5,11 +5,14 @@ import * as cookieParser from 'cookie-parser';
|
||||
import { VersioningType } from '@nestjs/common';
|
||||
import * as session from 'express-session';
|
||||
import { emitGQLSchemaFile } from './gql-schema';
|
||||
import { checkEnvironmentAuthProvider } from './utils';
|
||||
|
||||
async function bootstrap() {
|
||||
console.log(`Running in production: ${process.env.PRODUCTION}`);
|
||||
console.log(`Port: ${process.env.PORT}`);
|
||||
|
||||
checkEnvironmentAuthProvider();
|
||||
|
||||
const app = await NestFactory.create(AppModule);
|
||||
|
||||
app.use(
|
||||
|
||||
@@ -306,8 +306,8 @@ describe('TeamEnvironmentsService', () => {
|
||||
);
|
||||
|
||||
mockPrisma.teamEnvironment.create.mockResolvedValueOnce({
|
||||
...teamEnvironment,
|
||||
id: 'newid',
|
||||
...teamEnvironment,
|
||||
});
|
||||
|
||||
const result = await teamEnvironmentsService.createDuplicateEnvironment(
|
||||
@@ -337,8 +337,8 @@ describe('TeamEnvironmentsService', () => {
|
||||
);
|
||||
|
||||
mockPrisma.teamEnvironment.create.mockResolvedValueOnce({
|
||||
...teamEnvironment,
|
||||
id: 'newid',
|
||||
...teamEnvironment,
|
||||
});
|
||||
|
||||
const result = await teamEnvironmentsService.createDuplicateEnvironment(
|
||||
|
||||
@@ -9,7 +9,8 @@ import * as E from 'fp-ts/Either';
|
||||
import * as A from 'fp-ts/Array';
|
||||
import { TeamMemberRole } from './team/team.model';
|
||||
import { User } from './user/user.model';
|
||||
import { JSON_INVALID } from './errors';
|
||||
import { ENV_EMPTY_AUTH_PROVIDERS, ENV_NOT_FOUND_KEY_AUTH_PROVIDERS, ENV_NOT_SUPPORT_AUTH_PROVIDERS, JSON_INVALID } from './errors';
|
||||
import { AuthProvider } from './auth/helper';
|
||||
|
||||
/**
|
||||
* A workaround to throw an exception in an expression.
|
||||
@@ -152,3 +153,31 @@ export function isValidLength(title: string, length: number) {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called by bootstrap() in main.ts
|
||||
* It checks if the "ALLOWED_AUTH_PROVIDERS" environment variable is properly set or not.
|
||||
* If not, it throws an error.
|
||||
*/
|
||||
export function checkEnvironmentAuthProvider() {
|
||||
if (!process.env.hasOwnProperty('ALLOWED_AUTH_PROVIDERS')) {
|
||||
throw new Error(ENV_NOT_FOUND_KEY_AUTH_PROVIDERS);
|
||||
}
|
||||
|
||||
if (process.env.ALLOWED_AUTH_PROVIDERS === '') {
|
||||
throw new Error(ENV_EMPTY_AUTH_PROVIDERS);
|
||||
}
|
||||
|
||||
const givenAuthProviders = process.env.ALLOWED_AUTH_PROVIDERS.split(',').map(
|
||||
(provider) => provider.toLocaleUpperCase(),
|
||||
);
|
||||
const supportedAuthProviders = Object.values(AuthProvider).map(
|
||||
(provider: string) => provider.toLocaleUpperCase(),
|
||||
);
|
||||
|
||||
for (const givenAuthProvider of givenAuthProviders) {
|
||||
if (!supportedAuthProviders.includes(givenAuthProvider)) {
|
||||
throw new Error(ENV_NOT_SUPPORT_AUTH_PROVIDERS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
@apply after:backface-hidden;
|
||||
@apply selection:bg-accentDark;
|
||||
@apply selection:text-accentContrast;
|
||||
@apply overscroll-none;
|
||||
}
|
||||
|
||||
:root {
|
||||
|
||||
@@ -151,6 +151,11 @@
|
||||
"save_unsaved_tab": "Do you want to save changes made in this tab?",
|
||||
"sync": "Would you like to restore your workspace from cloud? This will discard your local progress."
|
||||
},
|
||||
"context_menu": {
|
||||
"set_environment_variable": "Set as variable",
|
||||
"add_parameter": "Add to parameter",
|
||||
"open_link_in_new_tab": "Open link in new tab"
|
||||
},
|
||||
"count": {
|
||||
"header": "Header {count}",
|
||||
"message": "Message {count}",
|
||||
@@ -195,16 +200,23 @@
|
||||
"created": "Environment created",
|
||||
"deleted": "Environment deletion",
|
||||
"edit": "Edit Environment",
|
||||
"global": "Global",
|
||||
"invalid_name": "Please provide a name for the environment",
|
||||
"my_environments": "My Environments",
|
||||
"name": "Name",
|
||||
"nested_overflow": "nested environment variables are limited to 10 levels",
|
||||
"new": "New Environment",
|
||||
"no_environment": "No environment",
|
||||
"no_environment_description": "No environments were selected. Choose what to do with the following variables.",
|
||||
"replace_with_variable": "Replace with variable",
|
||||
"scope": "Scope",
|
||||
"select": "Select environment",
|
||||
"set_as_environment": "Set as environment",
|
||||
"team_environments": "Team Environments",
|
||||
"title": "Environments",
|
||||
"updated": "Environment updated",
|
||||
"value": "Value",
|
||||
"variable": "Variable",
|
||||
"variable_list": "Variable List"
|
||||
},
|
||||
"error": {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"edit": "編輯",
|
||||
"filter": "篩選回應",
|
||||
"go_back": "返回",
|
||||
"go_forward": "Go forward",
|
||||
"go_forward": "向前",
|
||||
"group_by": "分組方式",
|
||||
"label": "標籤",
|
||||
"learn_more": "瞭解更多",
|
||||
@@ -117,37 +117,37 @@
|
||||
"username": "使用者名稱"
|
||||
},
|
||||
"collection": {
|
||||
"created": "組合已建立",
|
||||
"different_parent": "Cannot reorder collection with different parent",
|
||||
"edit": "編輯組合",
|
||||
"invalid_name": "請提供有效的組合名稱",
|
||||
"invalid_root_move": "Collection already in the root",
|
||||
"moved": "Moved Successfully",
|
||||
"my_collections": "我的組合",
|
||||
"name": "我的新組合",
|
||||
"name_length_insufficient": "組合名稱至少要有 3 個字元。",
|
||||
"new": "建立組合",
|
||||
"order_changed": "Collection Order Updated",
|
||||
"renamed": "組合已重新命名",
|
||||
"created": "集合已建立",
|
||||
"different_parent": "無法為父集合不同的集合重新排序",
|
||||
"edit": "編輯集合",
|
||||
"invalid_name": "請提供有效的集合名稱",
|
||||
"invalid_root_move": "集合已在根目錄",
|
||||
"moved": "移動成功",
|
||||
"my_collections": "我的集合",
|
||||
"name": "我的新集合",
|
||||
"name_length_insufficient": "集合名稱至少要有 3 個字元。",
|
||||
"new": "建立集合",
|
||||
"order_changed": "集合順序已更新",
|
||||
"renamed": "集合已重新命名",
|
||||
"request_in_use": "請求正在使用中",
|
||||
"save_as": "另存為",
|
||||
"select": "選擇一個組合",
|
||||
"select": "選擇一個集合",
|
||||
"select_location": "選擇位置",
|
||||
"select_team": "選擇一個團隊",
|
||||
"team_collections": "團隊組合"
|
||||
"team_collections": "團隊集合"
|
||||
},
|
||||
"confirm": {
|
||||
"exit_team": "您確定要離開此團隊嗎?",
|
||||
"logout": "您確定要登出嗎?",
|
||||
"remove_collection": "您確定要永久刪除該組合嗎?",
|
||||
"remove_collection": "您確定要永久刪除該集合嗎?",
|
||||
"remove_environment": "您確定要永久刪除該環境嗎?",
|
||||
"remove_folder": "您確定要永久刪除該資料夾嗎?",
|
||||
"remove_history": "您確定要永久刪除全部歷史記錄嗎?",
|
||||
"remove_request": "您確定要永久刪除該請求嗎?",
|
||||
"remove_team": "您確定要刪除該團隊嗎?",
|
||||
"remove_telemetry": "您確定要退出遙測服務嗎?",
|
||||
"request_change": "您確定要捨棄當前請求嗎?未儲存的變更將遺失。",
|
||||
"save_unsaved_tab": "Do you want to save changes made in this tab?",
|
||||
"request_change": "您確定要捨棄目前的請求嗎?未儲存的變更將遺失。",
|
||||
"save_unsaved_tab": "您要儲存在此分頁做出的改動嗎?",
|
||||
"sync": "您想從雲端恢復您的工作區嗎?這將丟棄您的本地進度。"
|
||||
},
|
||||
"count": {
|
||||
@@ -160,13 +160,13 @@
|
||||
},
|
||||
"documentation": {
|
||||
"generate": "產生文件",
|
||||
"generate_message": "匯入 Hoppscotch 組合以隨時隨地產生 API 文件。"
|
||||
"generate_message": "匯入 Hoppscotch 集合以隨時隨地產生 API 文件。"
|
||||
},
|
||||
"empty": {
|
||||
"authorization": "該請求沒有使用任何授權",
|
||||
"body": "該請求沒有任何請求主體",
|
||||
"collection": "組合為空",
|
||||
"collections": "組合為空",
|
||||
"collection": "集合為空",
|
||||
"collections": "集合為空",
|
||||
"documentation": "連線到 GraphQL 端點以檢視文件",
|
||||
"endpoint": "端點不能留空",
|
||||
"environments": "環境為空",
|
||||
@@ -209,7 +209,7 @@
|
||||
"browser_support_sse": "此瀏覽器似乎不支援 SSE。",
|
||||
"check_console_details": "檢查控制台日誌以獲悉詳情",
|
||||
"curl_invalid_format": "cURL 格式不正確",
|
||||
"danger_zone": "Danger zone",
|
||||
"danger_zone": "危險地帶",
|
||||
"delete_account": "您的帳號目前為這些團隊的擁有者:",
|
||||
"delete_account_description": "您在刪除帳號前必須先將您自己從團隊中移除、轉移擁有權,或是刪除團隊。",
|
||||
"empty_req_name": "空請求名稱",
|
||||
@@ -277,38 +277,38 @@
|
||||
"tests": "編寫測試指令碼以自動除錯。"
|
||||
},
|
||||
"hide": {
|
||||
"collection": "隱藏組合面板",
|
||||
"collection": "隱藏集合面板",
|
||||
"more": "隱藏更多",
|
||||
"preview": "隱藏預覽",
|
||||
"sidebar": "隱藏側邊欄"
|
||||
},
|
||||
"import": {
|
||||
"collections": "匯入組合",
|
||||
"collections": "匯入集合",
|
||||
"curl": "匯入 cURL",
|
||||
"failed": "匯入失敗",
|
||||
"from_gist": "從 Gist 匯入",
|
||||
"from_gist_description": "從 Gist 網址匯入",
|
||||
"from_insomnia": "從 Insomnia 匯入",
|
||||
"from_insomnia_description": "從 Insomnia 組合匯入",
|
||||
"from_insomnia_description": "從 Insomnia 集合匯入",
|
||||
"from_json": "從 Hoppscotch 匯入",
|
||||
"from_json_description": "從 Hoppscotch 組合檔匯入",
|
||||
"from_my_collections": "從我的組合匯入",
|
||||
"from_my_collections_description": "從我的組合檔匯入",
|
||||
"from_json_description": "從 Hoppscotch 集合檔匯入",
|
||||
"from_my_collections": "從我的集合匯入",
|
||||
"from_my_collections_description": "從我的集合檔匯入",
|
||||
"from_openapi": "從 OpenAPI 匯入",
|
||||
"from_openapi_description": "從 OpenAPI 規格檔 (YML/JSON) 匯入",
|
||||
"from_postman": "從 Postman 匯入",
|
||||
"from_postman_description": "從 Postman 組合匯入",
|
||||
"from_postman_description": "從 Postman 集合匯入",
|
||||
"from_url": "從網址匯入",
|
||||
"gist_url": "輸入 Gist 網址",
|
||||
"import_from_url_invalid_fetch": "無法從網址取得資料",
|
||||
"import_from_url_invalid_file_format": "匯入組合時發生錯誤",
|
||||
"import_from_url_invalid_file_format": "匯入集合時發生錯誤",
|
||||
"import_from_url_invalid_type": "不支援此類型。可接受的值為 'hoppscotch'、'openapi'、'postman'、'insomnia'",
|
||||
"import_from_url_success": "已匯入組合",
|
||||
"json_description": "從 Hoppscotch 組合 JSON 檔匯入組合",
|
||||
"import_from_url_success": "已匯入集合",
|
||||
"json_description": "從 Hoppscotch 集合 JSON 檔匯入集合",
|
||||
"title": "匯入"
|
||||
},
|
||||
"layout": {
|
||||
"collapse_collection": "隱藏或顯示組合",
|
||||
"collapse_collection": "隱藏或顯示集合",
|
||||
"collapse_sidebar": "隱藏或顯示側邊欄",
|
||||
"column": "垂直版面",
|
||||
"name": "配置",
|
||||
@@ -316,8 +316,8 @@
|
||||
"zen_mode": "專注模式"
|
||||
},
|
||||
"modal": {
|
||||
"close_unsaved_tab": "You have unsaved changes",
|
||||
"collections": "組合",
|
||||
"close_unsaved_tab": "您有未儲存的改動",
|
||||
"collections": "集合",
|
||||
"confirm": "確認",
|
||||
"edit_request": "編輯請求",
|
||||
"import_export": "匯入/匯出"
|
||||
@@ -374,9 +374,9 @@
|
||||
"email_verification_mail": "已將驗證信寄送至您的電子郵件地址。請點擊信中連結以驗證您的電子郵件地址。",
|
||||
"no_permission": "您沒有權限執行此操作。",
|
||||
"owner": "擁有者",
|
||||
"owner_description": "擁有者可以新增、編輯和刪除請求、組合和團隊成員。",
|
||||
"owner_description": "擁有者可以新增、編輯和刪除請求、集合和團隊成員。",
|
||||
"roles": "角色",
|
||||
"roles_description": "角色用來控制對共用組合的存取權。",
|
||||
"roles_description": "角色用來控制對共用集合的存取權。",
|
||||
"updated": "已更新個人檔案",
|
||||
"viewer": "檢視者",
|
||||
"viewer_description": "檢視者只能檢視和使用請求。"
|
||||
@@ -396,8 +396,8 @@
|
||||
"text": "文字"
|
||||
},
|
||||
"copy_link": "複製連結",
|
||||
"different_collection": "Cannot reorder requests from different collections",
|
||||
"duplicated": "Request duplicated",
|
||||
"different_collection": "無法重新排列來自不同集合的請求",
|
||||
"duplicated": "已複製請求",
|
||||
"duration": "持續時間",
|
||||
"enter_curl": "輸入 cURL",
|
||||
"generate_code": "產生程式碼",
|
||||
@@ -405,10 +405,10 @@
|
||||
"header_list": "請求標頭列表",
|
||||
"invalid_name": "請提供請求名稱",
|
||||
"method": "方法",
|
||||
"moved": "Request moved",
|
||||
"moved": "已移動請求",
|
||||
"name": "請求名稱",
|
||||
"new": "新請求",
|
||||
"order_changed": "Request Order Updated",
|
||||
"order_changed": "已更新請求順序",
|
||||
"override": "覆寫",
|
||||
"override_help": "在標頭設置 <kbd>Content-Type</kbd>",
|
||||
"overriden": "已覆寫",
|
||||
@@ -432,7 +432,7 @@
|
||||
"view_my_links": "檢視我的連結"
|
||||
},
|
||||
"response": {
|
||||
"audio": "Audio",
|
||||
"audio": "音訊",
|
||||
"body": "回應本體",
|
||||
"filter_response_body": "篩選 JSON 回應本體 (使用 JSONPath 語法)",
|
||||
"headers": "回應標頭",
|
||||
@@ -446,7 +446,7 @@
|
||||
"status": "狀態",
|
||||
"time": "時間",
|
||||
"title": "回應",
|
||||
"video": "Video",
|
||||
"video": "視訊",
|
||||
"waiting_for_connection": "等待連線",
|
||||
"xml": "XML"
|
||||
},
|
||||
@@ -494,7 +494,7 @@
|
||||
"short_codes_description": "我們為您打造的快捷碼。",
|
||||
"sidebar_on_left": "左側邊欄",
|
||||
"sync": "同步",
|
||||
"sync_collections": "組合",
|
||||
"sync_collections": "集合",
|
||||
"sync_description": "這些設定會同步到雲端。",
|
||||
"sync_environments": "環境",
|
||||
"sync_history": "歷史",
|
||||
@@ -551,7 +551,7 @@
|
||||
"previous_method": "選擇上一個方法",
|
||||
"put_method": "選擇 PUT 方法",
|
||||
"reset_request": "重置請求",
|
||||
"save_to_collections": "儲存到組合",
|
||||
"save_to_collections": "儲存到集合",
|
||||
"send_request": "傳送請求",
|
||||
"title": "請求"
|
||||
},
|
||||
@@ -570,7 +570,7 @@
|
||||
},
|
||||
"show": {
|
||||
"code": "顯示程式碼",
|
||||
"collection": "顯示組合面板",
|
||||
"collection": "顯示集合面板",
|
||||
"more": "顯示更多",
|
||||
"sidebar": "顯示側邊欄"
|
||||
},
|
||||
@@ -639,9 +639,9 @@
|
||||
"tab": {
|
||||
"authorization": "授權",
|
||||
"body": "請求本體",
|
||||
"collections": "組合",
|
||||
"collections": "集合",
|
||||
"documentation": "幫助文件",
|
||||
"environments": "Environments",
|
||||
"environments": "環境",
|
||||
"headers": "請求標頭",
|
||||
"history": "歷史記錄",
|
||||
"mqtt": "MQTT",
|
||||
@@ -666,7 +666,7 @@
|
||||
"email_do_not_match": "電子信箱與您的帳號資料不一致。請聯絡您的團隊擁有者。",
|
||||
"exit": "退出團隊",
|
||||
"exit_disabled": "團隊擁有者無法退出團隊",
|
||||
"invalid_coll_id": "Invalid collection ID",
|
||||
"invalid_coll_id": "集合 ID 無效",
|
||||
"invalid_email_format": "電子信箱格式無效",
|
||||
"invalid_id": "團隊 ID 無效。請聯絡您的團隊擁有者。",
|
||||
"invalid_invite_link": "邀請連結無效",
|
||||
@@ -690,21 +690,21 @@
|
||||
"member_removed": "使用者已移除",
|
||||
"member_role_updated": "使用者角色已更新",
|
||||
"members": "成員",
|
||||
"more_members": "+{count} more",
|
||||
"more_members": "還有 {count} 位",
|
||||
"name_length_insufficient": "團隊名稱至少為 6 個字元",
|
||||
"name_updated": "團隊名稱已更新",
|
||||
"new": "新團隊",
|
||||
"new_created": "已建立新團隊",
|
||||
"new_name": "我的新團隊",
|
||||
"no_access": "您沒有編輯組合的許可權",
|
||||
"no_access": "您沒有編輯集合的許可權",
|
||||
"no_invite_found": "未找到邀請。請聯絡您的團隊擁有者。",
|
||||
"no_request_found": "Request not found.",
|
||||
"no_request_found": "找不到請求。",
|
||||
"not_found": "找不到團隊。請聯絡您的團隊擁有者。",
|
||||
"not_valid_viewer": "您不是一個有效的檢視者。請聯絡您的團隊擁有者。",
|
||||
"parent_coll_move": "Cannot move collection to a child collection",
|
||||
"parent_coll_move": "無法將集合移動至子集合",
|
||||
"pending_invites": "待定邀請",
|
||||
"permissions": "許可權",
|
||||
"same_target_destination": "Same target and destination",
|
||||
"same_target_destination": "目標和目的地相同",
|
||||
"saved": "團隊已儲存",
|
||||
"select_a_team": "選擇團隊",
|
||||
"title": "團隊",
|
||||
@@ -734,9 +734,9 @@
|
||||
"url": "網址"
|
||||
},
|
||||
"workspace": {
|
||||
"change": "Change workspace",
|
||||
"personal": "My Workspace",
|
||||
"team": "Team Workspace",
|
||||
"title": "Workspaces"
|
||||
"change": "切換工作區",
|
||||
"personal": "我的工作區",
|
||||
"team": "團隊工作區",
|
||||
"title": "工作區"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@hoppscotch/common",
|
||||
"private": true,
|
||||
"version": "2023.4.7",
|
||||
"version": "2023.4.8",
|
||||
"scripts": {
|
||||
"dev": "pnpm exec npm-run-all -p -l dev:*",
|
||||
"test": "vitest --run",
|
||||
@@ -89,7 +89,6 @@
|
||||
"util": "^0.12.4",
|
||||
"uuid": "^8.3.2",
|
||||
"vue": "^3.2.25",
|
||||
"vue-github-button": "^3.0.3",
|
||||
"vue-i18n": "^9.2.2",
|
||||
"vue-pdf-embed": "^1.1.4",
|
||||
"vue-router": "^4.0.16",
|
||||
@@ -111,7 +110,7 @@
|
||||
"@graphql-codegen/typescript-urql-graphcache": "^2.3.1",
|
||||
"@graphql-codegen/urql-introspection": "^2.2.0",
|
||||
"@graphql-typed-document-node/core": "^3.1.1",
|
||||
"@iconify-json/lucide": "^1.1.40",
|
||||
"@iconify-json/lucide": "^1.1.109",
|
||||
"@intlify/vite-plugin-vue-i18n": "^7.0.0",
|
||||
"@relmify/jest-fp-ts": "^2.1.1",
|
||||
"@rushstack/eslint-patch": "^1.1.4",
|
||||
@@ -148,7 +147,7 @@
|
||||
"vite-plugin-html-config": "^1.0.10",
|
||||
"vite-plugin-inspect": "^0.7.4",
|
||||
"vite-plugin-pages": "^0.26.0",
|
||||
"vite-plugin-pages-sitemap": "^1.4.0",
|
||||
"vite-plugin-pages-sitemap": "^1.4.5",
|
||||
"vite-plugin-pwa": "^0.13.1",
|
||||
"vite-plugin-vue-layouts": "^0.7.0",
|
||||
"vite-plugin-windicss": "^1.8.8",
|
||||
|
||||
1
packages/hoppscotch-common/public/badge.svg
Normal file
1
packages/hoppscotch-common/public/badge.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="156" height="32" fill="none"><rect width="156" height="32" fill="#6366f1" rx="4"/><text xmlns="http://www.w3.org/2000/svg" x="50%" y="50%" fill="#fff" dominant-baseline="central" font-family="Helvetica,sans-serif" font-size="12" font-weight="bold" text-anchor="middle" text-rendering="geometricPrecision">▶ Run in Hoppscotch</text></svg>
|
||||
|
After Width: | Height: | Size: 389 B |
24
packages/hoppscotch-common/src/components.d.ts
vendored
24
packages/hoppscotch-common/src/components.d.ts
vendored
@@ -9,6 +9,7 @@ declare module '@vue/runtime-core' {
|
||||
export interface GlobalComponents {
|
||||
AppActionHandler: typeof import('./components/app/ActionHandler.vue')['default']
|
||||
AppAnnouncement: typeof import('./components/app/Announcement.vue')['default']
|
||||
AppContextMenu: typeof import('./components/app/ContextMenu.vue')['default']
|
||||
AppDeveloperOptions: typeof import('./components/app/DeveloperOptions.vue')['default']
|
||||
AppFooter: typeof import('./components/app/Footer.vue')['default']
|
||||
AppGitHubStarButton: typeof import('./components/app/GitHubStarButton.vue')['default']
|
||||
@@ -54,6 +55,7 @@ declare module '@vue/runtime-core' {
|
||||
CollectionsSaveRequest: typeof import('./components/collections/SaveRequest.vue')['default']
|
||||
CollectionsTeamCollections: typeof import('./components/collections/TeamCollections.vue')['default']
|
||||
Environments: typeof import('./components/environments/index.vue')['default']
|
||||
EnvironmentsAdd: typeof import('./components/environments/Add.vue')['default']
|
||||
EnvironmentsImportExport: typeof import('./components/environments/ImportExport.vue')['default']
|
||||
EnvironmentsMy: typeof import('./components/environments/my/index.vue')['default']
|
||||
EnvironmentsMyDetails: typeof import('./components/environments/my/Details.vue')['default']
|
||||
@@ -75,27 +77,6 @@ declare module '@vue/runtime-core' {
|
||||
History: typeof import('./components/history/index.vue')['default']
|
||||
HistoryGraphqlCard: typeof import('./components/history/graphql/Card.vue')['default']
|
||||
HistoryRestCard: typeof import('./components/history/rest/Card.vue')['default']
|
||||
HoppButtonPrimary: typeof import('@hoppscotch/ui')['HoppButtonPrimary']
|
||||
HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary']
|
||||
HoppSmartAnchor: typeof import('@hoppscotch/ui')['HoppSmartAnchor']
|
||||
HoppSmartAutoComplete: typeof import('@hoppscotch/ui')['HoppSmartAutoComplete']
|
||||
HoppSmartCheckbox: typeof import('@hoppscotch/ui')['HoppSmartCheckbox']
|
||||
HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal']
|
||||
HoppSmartExpand: typeof import('@hoppscotch/ui')['HoppSmartExpand']
|
||||
HoppSmartFileChip: typeof import('@hoppscotch/ui')['HoppSmartFileChip']
|
||||
HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem']
|
||||
HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink']
|
||||
HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal']
|
||||
HoppSmartPicture: typeof import('@hoppscotch/ui')['HoppSmartPicture']
|
||||
HoppSmartPlaceholder: typeof import('@hoppscotch/ui')['HoppSmartPlaceholder']
|
||||
HoppSmartProgressRing: typeof import('@hoppscotch/ui')['HoppSmartProgressRing']
|
||||
HoppSmartRadioGroup: typeof import('@hoppscotch/ui')['HoppSmartRadioGroup']
|
||||
HoppSmartSlideOver: typeof import('@hoppscotch/ui')['HoppSmartSlideOver']
|
||||
HoppSmartSpinner: typeof import('@hoppscotch/ui')['HoppSmartSpinner']
|
||||
HoppSmartTab: typeof import('@hoppscotch/ui')['HoppSmartTab']
|
||||
HoppSmartTabs: typeof import('@hoppscotch/ui')['HoppSmartTabs']
|
||||
HoppSmartWindow: typeof import('@hoppscotch/ui')['HoppSmartWindow']
|
||||
HoppSmartWindows: typeof import('@hoppscotch/ui')['HoppSmartWindows']
|
||||
HttpAuthorization: typeof import('./components/http/Authorization.vue')['default']
|
||||
HttpAuthorizationApiKey: typeof import('./components/http/authorization/ApiKey.vue')['default']
|
||||
HttpAuthorizationBasic: typeof import('./components/http/authorization/Basic.vue')['default']
|
||||
@@ -132,6 +113,7 @@ declare module '@vue/runtime-core' {
|
||||
IconLucideLayers: typeof import('~icons/lucide/layers')['default']
|
||||
IconLucideListEnd: typeof import('~icons/lucide/list-end')['default']
|
||||
IconLucideMinus: typeof import('~icons/lucide/minus')['default']
|
||||
IconLucideRss: typeof import('~icons/lucide/rss')['default']
|
||||
IconLucideSearch: typeof import('~icons/lucide/search')['default']
|
||||
IconLucideUsers: typeof import('~icons/lucide/users')['default']
|
||||
LensesHeadersRenderer: typeof import('./components/lenses/HeadersRenderer.vue')['default']
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<div
|
||||
ref="contextMenuRef"
|
||||
class="fixed bg-popover shadow-lg transform translate-y-8 border border-dividerDark p-2 rounded"
|
||||
:style="`top: ${position.top}px; left: ${position.left}px; z-index: 1000;`"
|
||||
>
|
||||
<div v-if="contextMenuOptions" class="flex flex-col">
|
||||
<div
|
||||
v-for="option in contextMenuOptions"
|
||||
:key="option.id"
|
||||
class="flex flex-col space-y-2"
|
||||
>
|
||||
<HoppSmartItem
|
||||
v-if="option.text.type === 'text' && option.text"
|
||||
:icon="option.icon"
|
||||
:label="option.text.text"
|
||||
@click="handleClick(option)"
|
||||
/>
|
||||
<component
|
||||
:is="option.text.component"
|
||||
v-else-if="option.text.type === 'custom'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onClickOutside } from "@vueuse/core"
|
||||
import { useService } from "dioc/vue"
|
||||
import { ref, watch } from "vue"
|
||||
import { ContextMenuResult, ContextMenuService } from "~/services/context-menu"
|
||||
import { EnvironmentMenuService } from "~/services/context-menu/menu/environment.menu"
|
||||
import { ParameterMenuService } from "~/services/context-menu/menu/parameter.menu"
|
||||
import { URLMenuService } from "~/services/context-menu/menu/url.menu"
|
||||
|
||||
const props = defineProps<{
|
||||
show: boolean
|
||||
position: { top: number; left: number }
|
||||
text: string | null
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "hide-modal"): void
|
||||
}>()
|
||||
|
||||
const contextMenuRef = ref<any | null>(null)
|
||||
|
||||
const contextMenuOptions = ref<ContextMenuResult[]>([])
|
||||
|
||||
onClickOutside(contextMenuRef, () => {
|
||||
emit("hide-modal")
|
||||
})
|
||||
|
||||
const contextMenuService = useService(ContextMenuService)
|
||||
|
||||
useService(EnvironmentMenuService)
|
||||
useService(ParameterMenuService)
|
||||
useService(URLMenuService)
|
||||
|
||||
const handleClick = (option: { action: () => void }) => {
|
||||
option.action()
|
||||
emit("hide-modal")
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [props.show, props.text],
|
||||
(val) => {
|
||||
if (val && props.text) {
|
||||
const options = contextMenuService.getMenuFor(props.text)
|
||||
contextMenuOptions.value = options
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
</script>
|
||||
@@ -15,18 +15,21 @@
|
||||
:label="t('app.name')"
|
||||
to="/"
|
||||
/>
|
||||
<!-- <AppGitHubStarButton class="mt-1.5 transition" /> -->
|
||||
</div>
|
||||
<div class="inline-flex items-center space-x-2">
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
||||
:title="`${t(
|
||||
'app.search'
|
||||
)} <kbd>${getPlatformSpecialKey()}</kbd> <kbd>K</kbd>`"
|
||||
:icon="IconSearch"
|
||||
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
|
||||
<div class="inline-flex items-center justify-center flex-1 space-x-2">
|
||||
<button
|
||||
class="flex flex-1 items-center justify-between px-2 py-1 bg-primaryDark transition text-secondaryLight cursor-text rounded border border-dividerDark max-w-xs hover:border-dividerDark hover:bg-primaryLight hover:text-secondary focus-visible:border-dividerDark focus-visible:bg-primaryLight focus-visible:text-secondary"
|
||||
@click="invokeAction('modals.search.toggle')"
|
||||
/>
|
||||
>
|
||||
<span class="inline-flex flex-1 items-center">
|
||||
<icon-lucide-search class="mr-2 svg-icons" />
|
||||
{{ t("app.search") }}
|
||||
</span>
|
||||
<span class="flex">
|
||||
<kbd class="shortcut-key">{{ getPlatformSpecialKey() }}</kbd>
|
||||
<kbd class="shortcut-key">K</kbd>
|
||||
</span>
|
||||
</button>
|
||||
<HoppButtonSecondary
|
||||
v-if="showInstallButton"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
@@ -44,6 +47,8 @@
|
||||
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
|
||||
@click="invokeAction('modals.support.toggle')"
|
||||
/>
|
||||
</div>
|
||||
<div class="inline-flex items-center justify-end flex-1 space-x-2">
|
||||
<div
|
||||
v-if="currentUser === null"
|
||||
class="inline-flex items-center space-x-2"
|
||||
@@ -238,7 +243,6 @@ import IconDownload from "~icons/lucide/download"
|
||||
import IconUploadCloud from "~icons/lucide/upload-cloud"
|
||||
import IconUserPlus from "~icons/lucide/user-plus"
|
||||
import IconLifeBuoy from "~icons/lucide/life-buoy"
|
||||
import IconSearch from "~icons/lucide/search"
|
||||
import { breakpointsTailwind, useBreakpoints, useNetwork } from "@vueuse/core"
|
||||
import { pwaDefferedPrompt, installPWA } from "@modules/pwa"
|
||||
import { platform } from "~/platform"
|
||||
|
||||
@@ -15,7 +15,10 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col divide-y divide-dividerLight">
|
||||
<HoppSmartPlaceholder v-if="isEmpty(shortcutsResults)">
|
||||
<HoppSmartPlaceholder
|
||||
v-if="isEmpty(shortcutsResults)"
|
||||
:text="`${t('state.nothing_found')} ‟${filterText}”`"
|
||||
>
|
||||
<icon-lucide-search class="pb-2 opacity-75 svg-icons" />
|
||||
</HoppSmartPlaceholder>
|
||||
<details
|
||||
|
||||
@@ -22,10 +22,11 @@
|
||||
</div>
|
||||
<div class="flex">
|
||||
<kbd class="shortcut-key">{{ getSpecialKey() }}</kbd>
|
||||
<kbd class="shortcut-key">K</kbd>
|
||||
<kbd class="shortcut-key">/</kbd>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<kbd class="shortcut-key">/</kbd>
|
||||
<kbd class="shortcut-key">{{ getSpecialKey() }}</kbd>
|
||||
<kbd class="shortcut-key">K</kbd>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<kbd class="shortcut-key">?</kbd>
|
||||
|
||||
@@ -193,7 +193,7 @@ import IconTrash2 from "~icons/lucide/trash-2"
|
||||
import IconEdit from "~icons/lucide/edit"
|
||||
import IconFolder from "~icons/lucide/folder"
|
||||
import IconFolderOpen from "~icons/lucide/folder-open"
|
||||
import { PropType, ref, computed, watch } from "vue"
|
||||
import { ref, computed, watch } from "vue"
|
||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { TippyComponent } from "vue-tippy"
|
||||
@@ -209,67 +209,36 @@ type FolderType = "collection" | "folder"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
default: "",
|
||||
required: true,
|
||||
},
|
||||
parentID: {
|
||||
type: String as PropType<string | null>,
|
||||
default: null,
|
||||
required: false,
|
||||
},
|
||||
data: {
|
||||
type: Object as PropType<HoppCollection<HoppRESTRequest> | TeamCollection>,
|
||||
default: () => ({}),
|
||||
required: true,
|
||||
},
|
||||
collectionsType: {
|
||||
type: String as PropType<CollectionType>,
|
||||
default: "my-collections",
|
||||
required: true,
|
||||
},
|
||||
/**
|
||||
* Collection component can be used for both collections and folders.
|
||||
* folderType is used to determine which one it is.
|
||||
*/
|
||||
folderType: {
|
||||
type: String as PropType<FolderType>,
|
||||
default: "collection",
|
||||
required: true,
|
||||
},
|
||||
isOpen: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: true,
|
||||
},
|
||||
isSelected: {
|
||||
type: Boolean as PropType<boolean | null>,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
exportLoading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
hasNoTeamAccess: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
collectionMoveLoading: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => [],
|
||||
required: false,
|
||||
},
|
||||
isLastItem: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
})
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
id: string
|
||||
parentID?: string | null
|
||||
data: HoppCollection<HoppRESTRequest> | TeamCollection
|
||||
/**
|
||||
* Collection component can be used for both collections and folders.
|
||||
* folderType is used to determine which one it is.
|
||||
*/
|
||||
collectionsType: CollectionType
|
||||
folderType: FolderType
|
||||
isOpen: boolean
|
||||
isSelected?: boolean | null
|
||||
exportLoading?: boolean
|
||||
hasNoTeamAccess?: boolean
|
||||
collectionMoveLoading?: string[]
|
||||
isLastItem?: boolean
|
||||
}>(),
|
||||
{
|
||||
id: "",
|
||||
parentID: null,
|
||||
collectionsType: "my-collections",
|
||||
folderType: "collection",
|
||||
isOpen: false,
|
||||
isSelected: false,
|
||||
exportLoading: false,
|
||||
hasNoTeamAccess: false,
|
||||
isLastItem: false,
|
||||
}
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: "toggle-children"): void
|
||||
@@ -448,8 +417,13 @@ const notSameDestination = computed(() => {
|
||||
})
|
||||
|
||||
const isCollLoading = computed(() => {
|
||||
if (props.collectionMoveLoading.length > 0 && props.data.id) {
|
||||
return props.collectionMoveLoading.includes(props.data.id)
|
||||
const { collectionMoveLoading } = props
|
||||
if (
|
||||
collectionMoveLoading &&
|
||||
collectionMoveLoading.length > 0 &&
|
||||
props.data.id
|
||||
) {
|
||||
return collectionMoveLoading.includes(props.data.id)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
||||
208
packages/hoppscotch-common/src/components/environments/Add.vue
Normal file
208
packages/hoppscotch-common/src/components/environments/Add.vue
Normal file
@@ -0,0 +1,208 @@
|
||||
<template>
|
||||
<HoppSmartModal
|
||||
v-if="show"
|
||||
:title="t('environment.set_as_environment')"
|
||||
@close="hideModal"
|
||||
>
|
||||
<template #body>
|
||||
<div class="flex space-y-4 flex-1 flex-col">
|
||||
<div class="flex items-center space-x-8 ml-2">
|
||||
<label for="name" class="font-semibold min-w-10">{{
|
||||
t("environment.name")
|
||||
}}</label>
|
||||
<input
|
||||
v-model="name"
|
||||
type="text"
|
||||
:placeholder="t('environment.variable')"
|
||||
class="input"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center space-x-8 ml-2">
|
||||
<label for="value" class="font-semibold min-w-10">{{
|
||||
t("environment.value")
|
||||
}}</label>
|
||||
<input type="text" :value="value" class="input" />
|
||||
</div>
|
||||
<div class="flex items-center space-x-8 ml-2">
|
||||
<label for="scope" class="font-semibold min-w-10">
|
||||
{{ t("environment.scope") }}
|
||||
</label>
|
||||
<div
|
||||
class="relative flex flex-1 flex-col border border-divider rounded focus-visible:border-dividerDark"
|
||||
>
|
||||
<EnvironmentsSelector v-model="scope" :is-scope-selector="true" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="replaceWithVariable" class="flex space-x-2 mt-3">
|
||||
<div class="min-w-18" />
|
||||
<HoppSmartCheckbox
|
||||
:on="replaceWithVariable"
|
||||
title="t('environment.replace_with_variable'))"
|
||||
@change="replaceWithVariable = !replaceWithVariable"
|
||||
/>
|
||||
<label for="replaceWithVariable">
|
||||
{{ t("environment.replace_with_variable") }}</label
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<span class="flex space-x-2">
|
||||
<HoppButtonPrimary
|
||||
:label="t('action.save')"
|
||||
outline
|
||||
@click="addEnvironment"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
:label="t('action.cancel')"
|
||||
outline
|
||||
filled
|
||||
@click="hideModal"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
</HoppSmartModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Environment } from "@hoppscotch/data"
|
||||
import { ref, watch } from "vue"
|
||||
import { useI18n } from "~/composables/i18n"
|
||||
import { useToast } from "~/composables/toast"
|
||||
import { GQLError } from "~/helpers/backend/GQLClient"
|
||||
import { TeamEnvironment } from "~/helpers/teams/TeamEnvironment"
|
||||
import {
|
||||
addEnvironmentVariable,
|
||||
addGlobalEnvVariable,
|
||||
} from "~/newstore/environments"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { updateTeamEnvironment } from "~/helpers/backend/mutations/TeamEnvironment"
|
||||
import { currentActiveTab } from "~/helpers/rest/tab"
|
||||
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
|
||||
const props = defineProps<{
|
||||
show: boolean
|
||||
position: { top: number; left: number }
|
||||
name: string
|
||||
value: string
|
||||
replaceWithVariable: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "hide-modal"): void
|
||||
}>()
|
||||
|
||||
const hideModal = () => {
|
||||
emit("hide-modal")
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.show,
|
||||
(newVal) => {
|
||||
if (!newVal) {
|
||||
scope.value = {
|
||||
type: "global",
|
||||
}
|
||||
name.value = ""
|
||||
replaceWithVariable.value = false
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
type Scope =
|
||||
| {
|
||||
type: "global"
|
||||
}
|
||||
| {
|
||||
type: "my-environment"
|
||||
environment: Environment
|
||||
index: number
|
||||
}
|
||||
| {
|
||||
type: "team-environment"
|
||||
environment: TeamEnvironment
|
||||
}
|
||||
|
||||
const scope = ref<Scope>({
|
||||
type: "global",
|
||||
})
|
||||
|
||||
const replaceWithVariable = ref(false)
|
||||
|
||||
const name = ref("")
|
||||
|
||||
const addEnvironment = async () => {
|
||||
if (!name.value) {
|
||||
toast.error(`${t("environment.invalid_name")}`)
|
||||
return
|
||||
}
|
||||
if (scope.value.type === "global") {
|
||||
addGlobalEnvVariable({
|
||||
key: name.value,
|
||||
value: props.value,
|
||||
})
|
||||
toast.success(`${t("environment.updated")}`)
|
||||
} else if (scope.value.type === "my-environment") {
|
||||
addEnvironmentVariable(scope.value.index, {
|
||||
key: name.value,
|
||||
value: props.value,
|
||||
})
|
||||
toast.success(`${t("environment.updated")}`)
|
||||
} else {
|
||||
const newVariables = [
|
||||
...scope.value.environment.environment.variables,
|
||||
{
|
||||
key: name.value,
|
||||
value: props.value,
|
||||
},
|
||||
]
|
||||
await pipe(
|
||||
updateTeamEnvironment(
|
||||
JSON.stringify(newVariables),
|
||||
scope.value.environment.id,
|
||||
scope.value.environment.environment.name
|
||||
),
|
||||
TE.match(
|
||||
(err: GQLError<string>) => {
|
||||
console.error(err)
|
||||
toast.error(`${getErrorMessage(err)}`)
|
||||
},
|
||||
() => {
|
||||
hideModal()
|
||||
toast.success(`${t("environment.updated")}`)
|
||||
}
|
||||
)
|
||||
)()
|
||||
}
|
||||
if (replaceWithVariable.value) {
|
||||
//replace the current tab endpoint with the variable name with << and >>
|
||||
const variableName = `<<${name.value}>>`
|
||||
//replace the currenttab endpoint containing the value in the text with variablename
|
||||
currentActiveTab.value.document.request.endpoint =
|
||||
currentActiveTab.value.document.request.endpoint.replace(
|
||||
props.value,
|
||||
variableName
|
||||
)
|
||||
}
|
||||
|
||||
hideModal()
|
||||
}
|
||||
|
||||
const getErrorMessage = (err: GQLError<string>) => {
|
||||
if (err.type === "network_error") {
|
||||
return t("error.network_error")
|
||||
} else {
|
||||
switch (err.error) {
|
||||
case "team_environment/not_found":
|
||||
return t("team_environment.not_found")
|
||||
case "Forbidden resource":
|
||||
return t("profile.no_permission")
|
||||
default:
|
||||
return t("error.something_went_wrong")
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -8,7 +8,7 @@
|
||||
<span
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="`${t('environment.select')}`"
|
||||
class="bg-transparent border-b border-dividerLight select-wrapper"
|
||||
class="bg-transparent select-wrapper"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
:icon="IconLayers"
|
||||
@@ -22,6 +22,7 @@
|
||||
class="flex-1 !justify-start pr-8 rounded-none"
|
||||
/>
|
||||
</span>
|
||||
|
||||
<template #content="{ hide }">
|
||||
<div
|
||||
ref="tippyActions"
|
||||
@@ -31,6 +32,7 @@
|
||||
@keyup.escape="hide()"
|
||||
>
|
||||
<HoppSmartItem
|
||||
v-if="!isScopeSelector"
|
||||
:label="`${t('environment.no_environment')}`"
|
||||
:info-icon="
|
||||
selectedEnvironmentIndex.type === 'NO_ENV_SELECTED'
|
||||
@@ -47,6 +49,21 @@
|
||||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
v-else-if="isScopeSelector && modelValue"
|
||||
:label="t('environment.global')"
|
||||
:icon="IconGlobe"
|
||||
:info-icon="modelValue.type === 'global' ? IconCheck : undefined"
|
||||
:active-info-icon="modelValue.type === 'global'"
|
||||
@click="
|
||||
() => {
|
||||
$emit('update:modelValue', {
|
||||
type: 'global',
|
||||
})
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartTabs
|
||||
v-model="selectedEnvTab"
|
||||
styles="sticky overflow-x-auto my-2 border border-divider rounded flex-shrink-0 z-10 top-0 bg-primary"
|
||||
@@ -61,11 +78,14 @@
|
||||
:key="`gen-${index}`"
|
||||
:icon="IconLayers"
|
||||
:label="gen.name"
|
||||
:info-icon="index === selectedEnv.index ? IconCheck : undefined"
|
||||
:active-info-icon="index === selectedEnv.index"
|
||||
:info-icon="isEnvActive(index) ? IconCheck : undefined"
|
||||
:active-info-icon="isEnvActive(index)"
|
||||
@click="
|
||||
() => {
|
||||
selectedEnvironmentIndex = { type: 'MY_ENV', index: index }
|
||||
handleEnvironmentChange(index, {
|
||||
type: 'my-environment',
|
||||
environment: gen,
|
||||
})
|
||||
hide()
|
||||
}
|
||||
"
|
||||
@@ -96,18 +116,14 @@
|
||||
:key="`gen-team-${index}`"
|
||||
:icon="IconLayers"
|
||||
:label="gen.environment.name"
|
||||
:info-icon="
|
||||
gen.id === selectedEnv.teamEnvID ? IconCheck : undefined
|
||||
"
|
||||
:active-info-icon="gen.id === selectedEnv.teamEnvID"
|
||||
:info-icon="isEnvActive(gen.id) ? IconCheck : undefined"
|
||||
:active-info-icon="isEnvActive(gen.id)"
|
||||
@click="
|
||||
() => {
|
||||
selectedEnvironmentIndex = {
|
||||
type: 'TEAM_ENV',
|
||||
teamEnvID: gen.id,
|
||||
teamID: gen.teamID,
|
||||
environment: gen.environment,
|
||||
}
|
||||
handleEnvironmentChange(index, {
|
||||
type: 'team-environment',
|
||||
environment: gen,
|
||||
})
|
||||
hide()
|
||||
}
|
||||
"
|
||||
@@ -136,9 +152,10 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, watch } from "vue"
|
||||
import { computed, onMounted, ref, watch } from "vue"
|
||||
import IconCheck from "~icons/lucide/check"
|
||||
import IconLayers from "~icons/lucide/layers"
|
||||
import IconGlobe from "~icons/lucide/globe"
|
||||
import { TippyComponent } from "vue-tippy"
|
||||
import { useI18n } from "~/composables/i18n"
|
||||
import { GQLError } from "~/helpers/backend/GQLClient"
|
||||
@@ -156,6 +173,31 @@ import TeamListAdapter from "~/helpers/teams/TeamListAdapter"
|
||||
import { useLocalState } from "~/newstore/localstate"
|
||||
import { onLoggedIn } from "~/composables/auth"
|
||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
||||
import { TeamEnvironment } from "~/helpers/teams/TeamEnvironment"
|
||||
import { Environment } from "@hoppscotch/data"
|
||||
|
||||
type Scope =
|
||||
| {
|
||||
type: "global"
|
||||
}
|
||||
| {
|
||||
type: "my-environment"
|
||||
environment: Environment
|
||||
index: number
|
||||
}
|
||||
| {
|
||||
type: "team-environment"
|
||||
environment: TeamEnvironment
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
isScopeSelector?: boolean
|
||||
modelValue?: Scope
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:modelValue", data: Scope): void
|
||||
}>()
|
||||
|
||||
const breakpoints = useBreakpoints(breakpointsTailwind)
|
||||
const mdAndLarger = breakpoints.greater("md")
|
||||
@@ -170,6 +212,39 @@ const myEnvironments = useReadonlyStream(environments$, [])
|
||||
|
||||
const workspace = useReadonlyStream(workspaceStatus$, { type: "personal" })
|
||||
|
||||
// TeamList-Adapter
|
||||
const teamListAdapter = new TeamListAdapter(true)
|
||||
const myTeams = useReadonlyStream(teamListAdapter.teamList$, null)
|
||||
const teamListFetched = ref(false)
|
||||
const REMEMBERED_TEAM_ID = useLocalState("REMEMBERED_TEAM_ID")
|
||||
|
||||
onLoggedIn(() => {
|
||||
!teamListAdapter.isInitialized && teamListAdapter.initialize()
|
||||
})
|
||||
|
||||
const switchToTeamWorkspace = (team: GetMyTeamsQuery["myTeams"][number]) => {
|
||||
REMEMBERED_TEAM_ID.value = team.id
|
||||
changeWorkspace({
|
||||
teamID: team.id,
|
||||
teamName: team.name,
|
||||
type: "team",
|
||||
})
|
||||
}
|
||||
|
||||
watch(
|
||||
() => myTeams.value,
|
||||
(newTeams) => {
|
||||
if (newTeams && !teamListFetched.value) {
|
||||
teamListFetched.value = true
|
||||
if (REMEMBERED_TEAM_ID.value) {
|
||||
const team = newTeams.find((t) => t.id === REMEMBERED_TEAM_ID.value)
|
||||
if (team) switchToTeamWorkspace(team)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// TeamEnv List Adapter
|
||||
const teamEnvListAdapter = new TeamEnvironmentAdapter(undefined)
|
||||
const teamListLoading = useReadonlyStream(teamEnvListAdapter.loading$, false)
|
||||
const teamAdapterError = useReadonlyStream(teamEnvListAdapter.error$, null)
|
||||
@@ -204,63 +279,152 @@ watch(
|
||||
}
|
||||
)
|
||||
|
||||
// TeamList-Adapter
|
||||
const teamListAdapter = new TeamListAdapter(true)
|
||||
const myTeams = useReadonlyStream(teamListAdapter.teamList$, null)
|
||||
const teamListFetched = ref(false)
|
||||
const REMEMBERED_TEAM_ID = useLocalState("REMEMBERED_TEAM_ID")
|
||||
|
||||
onLoggedIn(() => {
|
||||
!teamListAdapter.isInitialized && teamListAdapter.initialize()
|
||||
})
|
||||
|
||||
const switchToTeamWorkspace = (team: GetMyTeamsQuery["myTeams"][number]) => {
|
||||
REMEMBERED_TEAM_ID.value = team.id
|
||||
changeWorkspace({
|
||||
teamID: team.id,
|
||||
teamName: team.name,
|
||||
type: "team",
|
||||
})
|
||||
}
|
||||
|
||||
watch(
|
||||
() => myTeams.value,
|
||||
(newTeams) => {
|
||||
if (newTeams && !teamListFetched.value) {
|
||||
teamListFetched.value = true
|
||||
if (REMEMBERED_TEAM_ID.value) {
|
||||
const team = newTeams.find((t) => t.id === REMEMBERED_TEAM_ID.value)
|
||||
if (team) switchToTeamWorkspace(team)
|
||||
const handleEnvironmentChange = (
|
||||
index: number,
|
||||
env?:
|
||||
| {
|
||||
type: "my-environment"
|
||||
environment: Environment
|
||||
}
|
||||
| {
|
||||
type: "team-environment"
|
||||
environment: TeamEnvironment
|
||||
}
|
||||
) => {
|
||||
if (props.isScopeSelector && env) {
|
||||
if (env.type === "my-environment") {
|
||||
emit("update:modelValue", {
|
||||
type: "my-environment",
|
||||
environment: env.environment,
|
||||
index,
|
||||
})
|
||||
} else if (env.type === "team-environment") {
|
||||
emit("update:modelValue", {
|
||||
type: "team-environment",
|
||||
environment: env.environment,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if (env && env.type === "my-environment") {
|
||||
selectedEnvironmentIndex.value = {
|
||||
type: "MY_ENV",
|
||||
index,
|
||||
}
|
||||
} else if (env && env.type === "team-environment") {
|
||||
selectedEnvironmentIndex.value = {
|
||||
type: "TEAM_ENV",
|
||||
teamEnvID: env.environment.id,
|
||||
teamID: env.environment.teamID,
|
||||
environment: env.environment.environment,
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const isEnvActive = (id: string | number) => {
|
||||
if (props.isScopeSelector) {
|
||||
if (props.modelValue?.type === "my-environment") {
|
||||
return props.modelValue.index === id
|
||||
} else if (props.modelValue?.type === "team-environment") {
|
||||
return (
|
||||
props.modelValue?.type === "team-environment" &&
|
||||
props.modelValue.environment &&
|
||||
props.modelValue.environment.id === id
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (selectedEnvironmentIndex.value.type === "MY_ENV") {
|
||||
return selectedEnv.value.index === id
|
||||
} else {
|
||||
return (
|
||||
selectedEnvironmentIndex.value.type === "TEAM_ENV" &&
|
||||
selectedEnv.value.teamEnvID === id
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const selectedEnv = computed(() => {
|
||||
if (selectedEnvironmentIndex.value.type === "MY_ENV") {
|
||||
return {
|
||||
type: "MY_ENV",
|
||||
index: selectedEnvironmentIndex.value.index,
|
||||
name: myEnvironments.value[selectedEnvironmentIndex.value.index].name,
|
||||
}
|
||||
} else if (selectedEnvironmentIndex.value.type === "TEAM_ENV") {
|
||||
const teamEnv = teamEnvironmentList.value.find(
|
||||
(env) =>
|
||||
env.id ===
|
||||
(selectedEnvironmentIndex.value.type === "TEAM_ENV" &&
|
||||
selectedEnvironmentIndex.value.teamEnvID)
|
||||
)
|
||||
if (teamEnv) {
|
||||
if (props.isScopeSelector) {
|
||||
if (props.modelValue?.type === "my-environment") {
|
||||
return {
|
||||
type: "MY_ENV",
|
||||
index: props.modelValue.index,
|
||||
name: props.modelValue.environment?.name,
|
||||
}
|
||||
} else if (props.modelValue?.type === "team-environment") {
|
||||
return {
|
||||
type: "TEAM_ENV",
|
||||
name: teamEnv.environment.name,
|
||||
teamEnvID: selectedEnvironmentIndex.value.teamEnvID,
|
||||
name: props.modelValue.environment.environment.name,
|
||||
teamEnvID: props.modelValue.environment.id,
|
||||
}
|
||||
} else {
|
||||
return { type: "global", name: "Global" }
|
||||
}
|
||||
} else {
|
||||
if (selectedEnvironmentIndex.value.type === "MY_ENV") {
|
||||
return {
|
||||
type: "MY_ENV",
|
||||
index: selectedEnvironmentIndex.value.index,
|
||||
name: myEnvironments.value[selectedEnvironmentIndex.value.index].name,
|
||||
}
|
||||
} else if (selectedEnvironmentIndex.value.type === "TEAM_ENV") {
|
||||
const teamEnv = teamEnvironmentList.value.find(
|
||||
(env) =>
|
||||
env.id ===
|
||||
(selectedEnvironmentIndex.value.type === "TEAM_ENV" &&
|
||||
selectedEnvironmentIndex.value.teamEnvID)
|
||||
)
|
||||
if (teamEnv) {
|
||||
return {
|
||||
type: "TEAM_ENV",
|
||||
name: teamEnv.environment.name,
|
||||
teamEnvID: selectedEnvironmentIndex.value.teamEnvID,
|
||||
}
|
||||
} else {
|
||||
return { type: "NO_ENV_SELECTED" }
|
||||
}
|
||||
} else {
|
||||
return { type: "NO_ENV_SELECTED" }
|
||||
}
|
||||
} else {
|
||||
return { type: "NO_ENV_SELECTED" }
|
||||
}
|
||||
})
|
||||
|
||||
// Set the selected environment as initial scope value
|
||||
onMounted(() => {
|
||||
if (props.isScopeSelector) {
|
||||
if (
|
||||
selectedEnvironmentIndex.value.type === "MY_ENV" &&
|
||||
selectedEnvironmentIndex.value.index !== undefined
|
||||
) {
|
||||
emit("update:modelValue", {
|
||||
type: "my-environment",
|
||||
environment: myEnvironments.value[selectedEnvironmentIndex.value.index],
|
||||
index: selectedEnvironmentIndex.value.index,
|
||||
})
|
||||
} else if (
|
||||
selectedEnvironmentIndex.value.type === "TEAM_ENV" &&
|
||||
selectedEnvironmentIndex.value.teamEnvID &&
|
||||
teamEnvironmentList.value &&
|
||||
teamEnvironmentList.value.length > 0
|
||||
) {
|
||||
const teamEnv = teamEnvironmentList.value.find(
|
||||
(env) =>
|
||||
env.id ===
|
||||
(selectedEnvironmentIndex.value.type === "TEAM_ENV" &&
|
||||
selectedEnvironmentIndex.value.teamEnvID)
|
||||
)
|
||||
if (teamEnv) {
|
||||
emit("update:modelValue", {
|
||||
type: "team-environment",
|
||||
environment: teamEnv,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
emit("update:modelValue", {
|
||||
type: "global",
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -26,6 +26,13 @@
|
||||
:editing-variable-name="editingVariableName"
|
||||
@hide-modal="displayModalEdit(false)"
|
||||
/>
|
||||
<EnvironmentsAdd
|
||||
:show="showModalNew"
|
||||
:name="editingVariableName"
|
||||
:value="editingVariableValue"
|
||||
:position="position"
|
||||
@hide-modal="displayModalNew(false)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -161,10 +168,18 @@ watch(
|
||||
}
|
||||
)
|
||||
|
||||
const showModalNew = ref(false)
|
||||
const showModalDetails = ref(false)
|
||||
const action = ref<"new" | "edit">("edit")
|
||||
const editingEnvironmentIndex = ref<"Global" | null>(null)
|
||||
const editingVariableName = ref("")
|
||||
const editingVariableValue = ref("")
|
||||
|
||||
const position = ref({ top: 0, left: 0 })
|
||||
|
||||
const displayModalNew = (shouldDisplay: boolean) => {
|
||||
showModalNew.value = shouldDisplay
|
||||
}
|
||||
|
||||
const displayModalEdit = (shouldDisplay: boolean) => {
|
||||
action.value = "edit"
|
||||
@@ -233,4 +248,10 @@ watch(
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
defineActionHandler("modals.environment.add", ({ envName, variableName }) => {
|
||||
editingVariableName.value = envName
|
||||
editingVariableValue.value = variableName
|
||||
displayModalNew(true)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -61,7 +61,8 @@ import { useReadonlyStream } from "@composables/stream"
|
||||
import { AggregateEnvironment, aggregateEnvs$ } from "~/newstore/environments"
|
||||
import { platform } from "~/platform"
|
||||
import { useI18n } from "~/composables/i18n"
|
||||
import { onClickOutside } from "@vueuse/core"
|
||||
import { onClickOutside, useDebounceFn } from "@vueuse/core"
|
||||
import { invokeAction } from "~/helpers/actions"
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
@@ -149,6 +150,11 @@ const handleKeystroke = (ev: KeyboardEvent) => {
|
||||
ev.preventDefault()
|
||||
}
|
||||
|
||||
if (ev.shiftKey) {
|
||||
showSuggestionPopover.value = false
|
||||
return
|
||||
}
|
||||
|
||||
showSuggestionPopover.value = true
|
||||
|
||||
if (
|
||||
@@ -299,8 +305,46 @@ const envVars = computed(() =>
|
||||
const envTooltipPlugin = new HoppReactiveEnvPlugin(envVars, view)
|
||||
|
||||
const initView = (el: any) => {
|
||||
function handleTextSelection() {
|
||||
const selection = view.value?.state.selection.main
|
||||
if (selection) {
|
||||
const from = selection.from
|
||||
const to = selection.to
|
||||
const text = view.value?.state.doc.sliceString(from, to)
|
||||
const { top, left } = view.value?.coordsAtPos(from)
|
||||
if (text) {
|
||||
invokeAction("contextmenu.open", {
|
||||
position: {
|
||||
top,
|
||||
left,
|
||||
},
|
||||
text,
|
||||
})
|
||||
showSuggestionPopover.value = false
|
||||
} else {
|
||||
invokeAction("contextmenu.open", {
|
||||
position: {
|
||||
top,
|
||||
left,
|
||||
},
|
||||
text: null,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Debounce to prevent double click from selecting the word
|
||||
const debounceFn = useDebounceFn(() => {
|
||||
handleTextSelection()
|
||||
}, 140)
|
||||
|
||||
el.addEventListener("mouseup", debounceFn)
|
||||
el.addEventListener("keyup", debounceFn)
|
||||
|
||||
const extensions: Extension = [
|
||||
EditorView.lineWrapping,
|
||||
EditorView.contentAttributes.of({ "aria-label": props.placeholder }),
|
||||
EditorView.contentAttributes.of({ "data-enable-grammarly": "false" }),
|
||||
EditorView.updateListener.of((update) => {
|
||||
if (props.readonly) {
|
||||
update.view.contentDOM.inputMode = "none"
|
||||
@@ -431,7 +475,7 @@ watch(editor, () => {
|
||||
@apply border-b border-x border-divider;
|
||||
@apply overflow-y-auto;
|
||||
@apply -left-[1px];
|
||||
@apply right-0;
|
||||
@apply -right-[1px];
|
||||
|
||||
top: calc(100% + 1px);
|
||||
border-radius: 0 0 8px 8px;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
class="flex flex-col flex-1"
|
||||
>
|
||||
<SmartTreeBranch
|
||||
:root-nodes-length="rootNodes.data.length"
|
||||
:node-item="rootNode"
|
||||
:adapter="adapter as SmartTreeAdapter<T>"
|
||||
>
|
||||
|
||||
@@ -85,19 +85,25 @@ const props = defineProps<{
|
||||
* The node item that will be used to render the tree branch content
|
||||
*/
|
||||
nodeItem: TreeNode<T>
|
||||
/**
|
||||
* Total number of rootNode
|
||||
*/
|
||||
rootNodesLength?: number
|
||||
}>()
|
||||
|
||||
const CHILD_SLOT_NAME = "default"
|
||||
const t = useI18n()
|
||||
|
||||
const isOnlyRootChild = computed(() => props.rootNodesLength === 1)
|
||||
|
||||
/**
|
||||
* Marks whether the children on this branch were ever rendered
|
||||
* See the usage inside '<template>' for more info
|
||||
*/
|
||||
const childrenRendered = ref(false)
|
||||
const childrenRendered = ref(isOnlyRootChild.value)
|
||||
|
||||
const showChildren = ref(false)
|
||||
const isNodeOpen = ref(false)
|
||||
const showChildren = ref(isOnlyRootChild.value)
|
||||
const isNodeOpen = ref(isOnlyRootChild.value)
|
||||
|
||||
const highlightNode = ref(false)
|
||||
|
||||
|
||||
@@ -40,6 +40,8 @@ import {
|
||||
import { HoppEnvironmentPlugin } from "@helpers/editor/extensions/HoppEnvironment"
|
||||
import xmlFormat from "xml-formatter"
|
||||
import { platform } from "~/platform"
|
||||
import { invokeAction } from "~/helpers/actions"
|
||||
import { useDebounceFn } from "@vueuse/core"
|
||||
// TODO: Migrate from legacy mode
|
||||
|
||||
type ExtendedEditorConfig = {
|
||||
@@ -218,6 +220,40 @@ export function useCodemirror(
|
||||
ViewPlugin.fromClass(
|
||||
class {
|
||||
update(update: ViewUpdate) {
|
||||
function handleTextSelection() {
|
||||
const selection = view.value?.state.selection.main
|
||||
if (selection) {
|
||||
const from = selection.from
|
||||
const to = selection.to
|
||||
const text = view.value?.state.doc.sliceString(from, to)
|
||||
const { top, left } = view.value?.coordsAtPos(from)
|
||||
if (text) {
|
||||
invokeAction("contextmenu.open", {
|
||||
position: {
|
||||
top,
|
||||
left,
|
||||
},
|
||||
text,
|
||||
})
|
||||
} else {
|
||||
invokeAction("contextmenu.open", {
|
||||
position: {
|
||||
top,
|
||||
left,
|
||||
},
|
||||
text: null,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Debounce to prevent double click from selecting the word
|
||||
const debounceFn = useDebounceFn(() => {
|
||||
handleTextSelection()
|
||||
}, 140)
|
||||
|
||||
el.addEventListener("mouseup", debounceFn)
|
||||
el.addEventListener("keyup", debounceFn)
|
||||
const cursorPos = update.state.selection.main.head
|
||||
const line = update.state.doc.lineAt(cursorPos)
|
||||
|
||||
@@ -276,6 +312,7 @@ export function useCodemirror(
|
||||
run: indentLess,
|
||||
},
|
||||
]),
|
||||
EditorView.contentAttributes.of({ "data-enable-grammarly": "false" }),
|
||||
]
|
||||
|
||||
if (environmentTooltip) extensions.push(environmentTooltip.extension)
|
||||
|
||||
@@ -8,6 +8,7 @@ import { HoppRESTDocument } from "./rest/document"
|
||||
import { HoppGQLRequest } from "@hoppscotch/data"
|
||||
|
||||
export type HoppAction =
|
||||
| "contextmenu.open" // Send/Cancel a Hoppscotch Request
|
||||
| "request.send-cancel" // Send/Cancel a Hoppscotch Request
|
||||
| "request.reset" // Clear request data
|
||||
| "request.copy-link" // Copy Request Link
|
||||
@@ -24,6 +25,9 @@ export type HoppAction =
|
||||
| "modals.search.toggle" // Shows the search modal
|
||||
| "modals.support.toggle" // Shows the support modal
|
||||
| "modals.share.toggle" // Shows the share modal
|
||||
| "modals.environment.add" // Show add environment modal via context menu
|
||||
| "modals.my.environment.edit" // Edit current personal environment
|
||||
| "modals.team.environment.edit" // Edit current team environment
|
||||
| "navigation.jump.rest" // Jump to REST page
|
||||
| "navigation.jump.graphql" // Jump to GraphQL page
|
||||
| "navigation.jump.realtime" // Jump to realtime page
|
||||
@@ -54,6 +58,13 @@ export type HoppAction =
|
||||
* will know if you got something wrong if there is a type error in this file
|
||||
*/
|
||||
type HoppActionArgsMap = {
|
||||
"contextmenu.open": {
|
||||
position: {
|
||||
top: number
|
||||
left: number
|
||||
}
|
||||
text: string | null
|
||||
}
|
||||
"modals.my.environment.edit": {
|
||||
envName: string
|
||||
variableName: string
|
||||
@@ -68,6 +79,10 @@ type HoppActionArgsMap = {
|
||||
"gql.request.open": {
|
||||
request: HoppGQLRequest
|
||||
}
|
||||
"modals.environment.add": {
|
||||
envName: string
|
||||
variableName: string
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,7 +14,13 @@ let keybindingsEnabled = true
|
||||
* Alt is also regarded as macOS OPTION (⌥) key
|
||||
* Ctrl is also regarded as macOS COMMAND (⌘) key (NOTE: this differs from HTML Keyboard spec where COMMAND is Meta key!)
|
||||
*/
|
||||
type ModifierKeys = "ctrl" | "alt" | "ctrl-shift" | "alt-shift"
|
||||
type ModifierKeys =
|
||||
| "ctrl"
|
||||
| "alt"
|
||||
| "ctrl-shift"
|
||||
| "alt-shift"
|
||||
| "ctrl-alt"
|
||||
| "ctrl-alt-shift"
|
||||
|
||||
/* eslint-disable prettier/prettier */
|
||||
// prettier-ignore
|
||||
@@ -143,18 +149,19 @@ function getPressedKey(ev: KeyboardEvent): Key | null {
|
||||
}
|
||||
|
||||
function getActiveModifier(ev: KeyboardEvent): ModifierKeys | null {
|
||||
const isShiftKey = ev.shiftKey
|
||||
const modifierKeys = {
|
||||
ctrl: isAppleDevice() ? ev.metaKey : ev.ctrlKey,
|
||||
alt: ev.altKey,
|
||||
shift: ev.shiftKey,
|
||||
}
|
||||
|
||||
// We only allow one modifier key to be pressed (for now)
|
||||
// Control key (+ Command) gets priority and if Alt is also pressed, it is ignored
|
||||
if (isAppleDevice() && ev.metaKey) return isShiftKey ? "ctrl-shift" : "ctrl"
|
||||
else if (!isAppleDevice() && ev.ctrlKey)
|
||||
return isShiftKey ? "ctrl-shift" : "ctrl"
|
||||
// active modifier: ctrl | alt | ctrl-alt | ctrl-shift | ctrl-alt-shift | alt-shift
|
||||
// modiferKeys object's keys are sorted to match the above order
|
||||
const activeModifier = Object.keys(modifierKeys)
|
||||
.filter((key) => modifierKeys[key as keyof typeof modifierKeys])
|
||||
.join("-")
|
||||
|
||||
// Test for Alt key
|
||||
if (ev.altKey) return isShiftKey ? "alt-shift" : "alt"
|
||||
|
||||
return null
|
||||
return activeModifier === "" ? null : (activeModifier as ModifierKeys)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,12 +16,12 @@ export function getShortcuts(t: (x: string) => string): ShortcutDef[] {
|
||||
},
|
||||
{
|
||||
label: t("shortcut.general.command_menu"),
|
||||
keys: ["/"],
|
||||
keys: [getPlatformSpecialKey(), "K"],
|
||||
section: t("shortcut.general.title"),
|
||||
},
|
||||
{
|
||||
label: t("shortcut.general.show_all"),
|
||||
keys: [getPlatformSpecialKey(), "K"],
|
||||
keys: [getPlatformSpecialKey(), "/"],
|
||||
section: t("shortcut.general.title"),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -84,6 +84,13 @@
|
||||
:show="savingRequest"
|
||||
@hide-modal="onSaveModalClose"
|
||||
/>
|
||||
<AppContextMenu
|
||||
v-if="contextMenu.show"
|
||||
:show="contextMenu.show"
|
||||
:position="contextMenu.position"
|
||||
:text="contextMenu.text"
|
||||
@hide-modal="contextMenu.show = false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -138,6 +145,24 @@ const reqName = ref<string>("")
|
||||
const t = useI18n()
|
||||
const toast = useToast()
|
||||
|
||||
type PopupDetails = {
|
||||
show: boolean
|
||||
position: {
|
||||
top: number
|
||||
left: number
|
||||
}
|
||||
text: string | null
|
||||
}
|
||||
|
||||
const contextMenu = ref<PopupDetails>({
|
||||
show: false,
|
||||
position: {
|
||||
top: 0,
|
||||
left: 0,
|
||||
},
|
||||
text: null,
|
||||
})
|
||||
|
||||
const tabs = getActiveTabs()
|
||||
|
||||
const confirmSync = useReadonlyStream(currentSyncingStatus$, {
|
||||
@@ -365,6 +390,22 @@ function oAuthURL() {
|
||||
})
|
||||
}
|
||||
|
||||
defineActionHandler("contextmenu.open", ({ position, text }) => {
|
||||
if (text) {
|
||||
contextMenu.value = {
|
||||
show: true,
|
||||
position,
|
||||
text,
|
||||
}
|
||||
} else {
|
||||
contextMenu.value = {
|
||||
show: false,
|
||||
position,
|
||||
text,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
setupTabStateSync()
|
||||
bindRequestToURLParams()
|
||||
oAuthURL()
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
import { describe, it, expect, vi } from "vitest"
|
||||
import { ContextMenu, ContextMenuResult, ContextMenuService } from "../"
|
||||
import { TestContainer } from "dioc/testing"
|
||||
|
||||
const contextMenuResult: ContextMenuResult[] = [
|
||||
{
|
||||
id: "result1",
|
||||
text: { type: "text", text: "Sample Text" },
|
||||
icon: {},
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
action: () => {},
|
||||
},
|
||||
]
|
||||
|
||||
const testMenu: ContextMenu = {
|
||||
menuID: "menu1",
|
||||
getMenuFor: () => {
|
||||
return {
|
||||
results: contextMenuResult,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
describe("ContextMenuService", () => {
|
||||
describe("registerMenu", () => {
|
||||
it("should register a menu", () => {
|
||||
const container = new TestContainer()
|
||||
const service = container.bind(ContextMenuService)
|
||||
|
||||
service.registerMenu(testMenu)
|
||||
|
||||
const result = service.getMenuFor("text")
|
||||
|
||||
expect(result).toContainEqual(expect.objectContaining({ id: "result1" }))
|
||||
})
|
||||
|
||||
it("should not register a menu twice", () => {
|
||||
const container = new TestContainer()
|
||||
const service = container.bind(ContextMenuService)
|
||||
|
||||
service.registerMenu(testMenu)
|
||||
service.registerMenu(testMenu)
|
||||
|
||||
const result = service.getMenuFor("text")
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
})
|
||||
|
||||
it("should register multiple menus", () => {
|
||||
const container = new TestContainer()
|
||||
const service = container.bind(ContextMenuService)
|
||||
|
||||
const testMenu2: ContextMenu = {
|
||||
menuID: "menu2",
|
||||
getMenuFor: () => {
|
||||
return {
|
||||
results: contextMenuResult,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
service.registerMenu(testMenu)
|
||||
service.registerMenu(testMenu2)
|
||||
|
||||
const result = service.getMenuFor("text")
|
||||
|
||||
expect(result).toHaveLength(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe("getMenuFor", () => {
|
||||
it("should get the menu", () => {
|
||||
const sampleMenus = {
|
||||
results: contextMenuResult,
|
||||
}
|
||||
|
||||
const container = new TestContainer()
|
||||
const service = container.bind(ContextMenuService)
|
||||
|
||||
service.registerMenu(testMenu)
|
||||
|
||||
const results = service.getMenuFor("sometext")
|
||||
|
||||
expect(results).toEqual(sampleMenus.results)
|
||||
})
|
||||
|
||||
it("calls registered menus with correct value", () => {
|
||||
const container = new TestContainer()
|
||||
const service = container.bind(ContextMenuService)
|
||||
|
||||
const testMenu2: ContextMenu = {
|
||||
menuID: "some-id",
|
||||
getMenuFor: vi.fn(() => ({
|
||||
results: contextMenuResult,
|
||||
})),
|
||||
}
|
||||
|
||||
service.registerMenu(testMenu2)
|
||||
|
||||
service.getMenuFor("sometext")
|
||||
|
||||
expect(testMenu2.getMenuFor).toHaveBeenCalledWith("sometext")
|
||||
})
|
||||
|
||||
it("should return empty array if no menus are registered", () => {
|
||||
const container = new TestContainer()
|
||||
const service = container.bind(ContextMenuService)
|
||||
|
||||
const results = service.getMenuFor("sometext")
|
||||
|
||||
expect(results).toEqual([])
|
||||
})
|
||||
})
|
||||
})
|
||||
109
packages/hoppscotch-common/src/services/context-menu/index.ts
Normal file
109
packages/hoppscotch-common/src/services/context-menu/index.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { Service } from "dioc"
|
||||
import { Component } from "vue"
|
||||
|
||||
/**
|
||||
* Defines how to render the text in a Context Menu Search Result
|
||||
*/
|
||||
export type ContextMenuTextType<T extends object | Component = never> =
|
||||
| {
|
||||
type: "text"
|
||||
text: string
|
||||
}
|
||||
| {
|
||||
type: "custom"
|
||||
/**
|
||||
* The component to render in place of the text
|
||||
*/
|
||||
component: T
|
||||
|
||||
/**
|
||||
* The props to pass to the component
|
||||
*/
|
||||
componentProps: T extends Component<infer Props> ? Props : never
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines info about a context menu result so the UI can render it
|
||||
*/
|
||||
export interface ContextMenuResult {
|
||||
/**
|
||||
* The unique ID of the result
|
||||
*/
|
||||
id: string
|
||||
/**
|
||||
* The text to render in the result
|
||||
*/
|
||||
text: ContextMenuTextType<any>
|
||||
/**
|
||||
* The icon to render as the signifier of the result
|
||||
*/
|
||||
icon: object | Component
|
||||
/**
|
||||
* The action to perform when the result is selected
|
||||
*/
|
||||
action: () => void
|
||||
/**
|
||||
* Additional metadata about the result
|
||||
*/
|
||||
meta?: {
|
||||
/**
|
||||
* The keyboard shortcut to trigger the result
|
||||
*/
|
||||
keyboardShortcut?: string[]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the state of a context menu
|
||||
*/
|
||||
export type ContextMenuState = {
|
||||
results: ContextMenuResult[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a context menu
|
||||
*/
|
||||
export interface ContextMenu {
|
||||
/**
|
||||
* The unique ID of the context menu
|
||||
* This is used to identify the context menu
|
||||
*/
|
||||
menuID: string
|
||||
/**
|
||||
* Gets the context menu for the given text
|
||||
* @param text The text to get the context menu for
|
||||
* @returns The context menu state
|
||||
*/
|
||||
getMenuFor: (text: string) => ContextMenuState
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the context menu service
|
||||
* This service is used to register context menus and get context menus for text
|
||||
* This service is used by the context menu UI
|
||||
*/
|
||||
export class ContextMenuService extends Service {
|
||||
public static readonly ID = "CONTEXT_MENU_SERVICE"
|
||||
|
||||
private menus: Map<string, ContextMenu> = new Map()
|
||||
|
||||
/**
|
||||
* Registers a menu with the context menu service
|
||||
* @param menu The menu to register
|
||||
*/
|
||||
public registerMenu(menu: ContextMenu) {
|
||||
this.menus.set(menu.menuID, menu)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the context menu for the given text
|
||||
* @param text The text to get the context menu for
|
||||
*/
|
||||
public getMenuFor(text: string): ContextMenuResult[] {
|
||||
const menus = Array.from(this.menus.values()).map((x) => x.getMenuFor(text))
|
||||
|
||||
const result = menus.flatMap((x) => x.results)
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { TestContainer } from "dioc/testing"
|
||||
import { describe, expect, it, vi } from "vitest"
|
||||
import { EnvironmentMenuService } from "../environment.menu"
|
||||
import { ContextMenuService } from "../.."
|
||||
|
||||
vi.mock("~/modules/i18n", () => ({
|
||||
__esModule: true,
|
||||
getI18n: () => (x: string) => x,
|
||||
}))
|
||||
|
||||
const actionsMock = vi.hoisted(() => ({
|
||||
invokeAction: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock("~/helpers/actions", async () => {
|
||||
return {
|
||||
__esModule: true,
|
||||
invokeAction: actionsMock.invokeAction,
|
||||
}
|
||||
})
|
||||
|
||||
describe("EnvironmentMenuService", () => {
|
||||
it("registers with the contextmenu service upon initialization", () => {
|
||||
const container = new TestContainer()
|
||||
|
||||
const registerContextMenuFn = vi.fn()
|
||||
|
||||
container.bindMock(ContextMenuService, {
|
||||
registerMenu: registerContextMenuFn,
|
||||
})
|
||||
|
||||
const environment = container.bind(EnvironmentMenuService)
|
||||
|
||||
expect(registerContextMenuFn).toHaveBeenCalledOnce()
|
||||
expect(registerContextMenuFn).toHaveBeenCalledWith(environment)
|
||||
})
|
||||
|
||||
describe("getMenuFor", () => {
|
||||
it("should return a menu for adding environment", () => {
|
||||
const container = new TestContainer()
|
||||
const environment = container.bind(EnvironmentMenuService)
|
||||
|
||||
const test = "some-text"
|
||||
const result = environment.getMenuFor(test)
|
||||
|
||||
expect(result.results).toContainEqual(
|
||||
expect.objectContaining({ id: "environment" })
|
||||
)
|
||||
})
|
||||
|
||||
it("should invoke the add environment modal", () => {
|
||||
const container = new TestContainer()
|
||||
const environment = container.bind(EnvironmentMenuService)
|
||||
|
||||
const test = "some-text"
|
||||
const result = environment.getMenuFor(test)
|
||||
|
||||
const action = result.results[0].action
|
||||
action()
|
||||
expect(actionsMock.invokeAction).toHaveBeenCalledOnce()
|
||||
expect(actionsMock.invokeAction).toHaveBeenCalledWith(
|
||||
"modals.environment.add",
|
||||
{
|
||||
envName: "test",
|
||||
variableName: test,
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,94 @@
|
||||
import { TestContainer } from "dioc/testing"
|
||||
import { describe, expect, it, vi } from "vitest"
|
||||
import { ContextMenuService } from "../.."
|
||||
import { ParameterMenuService } from "../parameter.menu"
|
||||
|
||||
//regex containing both url and parameter
|
||||
const urlAndParameterRegex = new RegExp("[^&?]*?=[^&?]*")
|
||||
|
||||
vi.mock("~/modules/i18n", () => ({
|
||||
__esModule: true,
|
||||
getI18n: () => (x: string) => x,
|
||||
}))
|
||||
|
||||
const tabMock = vi.hoisted(() => ({
|
||||
currentActiveTab: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock("~/helpers/rest/tab", () => ({
|
||||
__esModule: true,
|
||||
currentActiveTab: tabMock.currentActiveTab,
|
||||
}))
|
||||
|
||||
describe("ParameterMenuService", () => {
|
||||
it("registers with the contextmenu service upon initialization", () => {
|
||||
const container = new TestContainer()
|
||||
|
||||
const registerContextMenuFn = vi.fn()
|
||||
|
||||
container.bindMock(ContextMenuService, {
|
||||
registerMenu: registerContextMenuFn,
|
||||
})
|
||||
|
||||
const parameter = container.bind(ParameterMenuService)
|
||||
|
||||
expect(registerContextMenuFn).toHaveBeenCalledOnce()
|
||||
expect(registerContextMenuFn).toHaveBeenCalledWith(parameter)
|
||||
|
||||
describe("getMenuFor", () => {
|
||||
it("validating if the text passes the regex and return the menu", () => {
|
||||
const container = new TestContainer()
|
||||
const parameter = container.bind(ParameterMenuService)
|
||||
|
||||
const test = "https://hoppscotch.io?id=some-text"
|
||||
const result = parameter.getMenuFor(test)
|
||||
|
||||
if (test.match(urlAndParameterRegex)) {
|
||||
expect(result.results).toContainEqual(
|
||||
expect.objectContaining({ id: "parameter" })
|
||||
)
|
||||
} else {
|
||||
expect(result.results).not.toContainEqual(
|
||||
expect.objectContaining({ id: "parameter" })
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it("should call the addParameter function when action is called", () => {
|
||||
const addParameterFn = vi.fn()
|
||||
|
||||
const container = new TestContainer()
|
||||
const environment = container.bind(ParameterMenuService)
|
||||
|
||||
const test = "https://hoppscotch.io"
|
||||
|
||||
const result = environment.getMenuFor(test)
|
||||
|
||||
const action = result.results[0].action
|
||||
|
||||
action()
|
||||
|
||||
expect(addParameterFn).toHaveBeenCalledOnce()
|
||||
expect(addParameterFn).toHaveBeenCalledWith(action)
|
||||
})
|
||||
|
||||
it("should call the extractParams function when addParameter function is called", () => {
|
||||
const extractParamsFn = vi.fn()
|
||||
|
||||
const container = new TestContainer()
|
||||
const environment = container.bind(ParameterMenuService)
|
||||
|
||||
const test = "https://hoppscotch.io"
|
||||
|
||||
const result = environment.getMenuFor(test)
|
||||
|
||||
const action = result.results[0].action
|
||||
|
||||
action()
|
||||
|
||||
expect(extractParamsFn).toHaveBeenCalledOnce()
|
||||
expect(extractParamsFn).toHaveBeenCalledWith(action)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,86 @@
|
||||
import { TestContainer } from "dioc/testing"
|
||||
import { describe, expect, it, vi } from "vitest"
|
||||
import { ContextMenuService } from "../.."
|
||||
import { URLMenuService } from "../url.menu"
|
||||
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
||||
|
||||
vi.mock("~/modules/i18n", () => ({
|
||||
__esModule: true,
|
||||
getI18n: () => (x: string) => x,
|
||||
}))
|
||||
|
||||
const tabMock = vi.hoisted(() => ({
|
||||
createNewTab: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock("~/helpers/rest/tab", () => ({
|
||||
__esModule: true,
|
||||
createNewTab: tabMock.createNewTab,
|
||||
}))
|
||||
|
||||
describe("URLMenuService", () => {
|
||||
it("registers with the contextmenu service upon initialization", () => {
|
||||
const container = new TestContainer()
|
||||
|
||||
const registerContextMenuFn = vi.fn()
|
||||
|
||||
container.bindMock(ContextMenuService, {
|
||||
registerMenu: registerContextMenuFn,
|
||||
})
|
||||
|
||||
const environment = container.bind(URLMenuService)
|
||||
|
||||
expect(registerContextMenuFn).toHaveBeenCalledOnce()
|
||||
expect(registerContextMenuFn).toHaveBeenCalledWith(environment)
|
||||
})
|
||||
|
||||
describe("getMenuFor", () => {
|
||||
it("validating if the text passes the regex and return the menu", () => {
|
||||
function isValidURL(url: string) {
|
||||
try {
|
||||
new URL(url)
|
||||
return true
|
||||
} catch (error) {
|
||||
// Fallback to regular expression check
|
||||
const pattern = /^(https?:\/\/)?([\w.-]+)(\.[\w.-]+)+([/?].*)?$/
|
||||
return pattern.test(url)
|
||||
}
|
||||
}
|
||||
|
||||
const container = new TestContainer()
|
||||
const url = container.bind(URLMenuService)
|
||||
|
||||
const test = ""
|
||||
const result = url.getMenuFor(test)
|
||||
|
||||
if (isValidURL(test)) {
|
||||
expect(result.results).toContainEqual(
|
||||
expect.objectContaining({ id: "link-tab" })
|
||||
)
|
||||
} else {
|
||||
expect(result).toEqual({ results: [] })
|
||||
}
|
||||
})
|
||||
|
||||
it("should call the openNewTab function when action is called and a new hoppscotch tab is opened", () => {
|
||||
const container = new TestContainer()
|
||||
const url = container.bind(URLMenuService)
|
||||
|
||||
const test = "https://hoppscotch.io"
|
||||
const result = url.getMenuFor(test)
|
||||
|
||||
result.results[0].action()
|
||||
|
||||
const request = {
|
||||
...getDefaultRESTRequest(),
|
||||
endpoint: test,
|
||||
}
|
||||
|
||||
expect(tabMock.createNewTab).toHaveBeenCalledOnce()
|
||||
expect(tabMock.createNewTab).toHaveBeenCalledWith({
|
||||
request: request,
|
||||
isDirty: false,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,56 @@
|
||||
import { Service } from "dioc"
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuResult,
|
||||
ContextMenuService,
|
||||
ContextMenuState,
|
||||
} from "../"
|
||||
import { markRaw, ref } from "vue"
|
||||
import { invokeAction } from "~/helpers/actions"
|
||||
import IconPlusCircle from "~icons/lucide/plus-circle"
|
||||
import { getI18n } from "~/modules/i18n"
|
||||
|
||||
/**
|
||||
* This menu returns a single result that allows the user
|
||||
* to add the selected text as an environment variable
|
||||
* This menus is shown on all text selections
|
||||
*/
|
||||
export class EnvironmentMenuService extends Service implements ContextMenu {
|
||||
public static readonly ID = "ENVIRONMENT_CONTEXT_MENU_SERVICE"
|
||||
|
||||
private t = getI18n()
|
||||
|
||||
public readonly menuID = "environment"
|
||||
|
||||
private readonly contextMenu = this.bind(ContextMenuService)
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
|
||||
this.contextMenu.registerMenu(this)
|
||||
}
|
||||
|
||||
getMenuFor(text: Readonly<string>): ContextMenuState {
|
||||
const results = ref<ContextMenuResult[]>([])
|
||||
results.value = [
|
||||
{
|
||||
id: "environment",
|
||||
text: {
|
||||
type: "text",
|
||||
text: this.t("context_menu.set_environment_variable"),
|
||||
},
|
||||
icon: markRaw(IconPlusCircle),
|
||||
action: () => {
|
||||
invokeAction("modals.environment.add", {
|
||||
envName: "test",
|
||||
variableName: text,
|
||||
})
|
||||
},
|
||||
},
|
||||
]
|
||||
const resultObj = <ContextMenuState>{
|
||||
results: results.value,
|
||||
}
|
||||
return resultObj
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import { Service } from "dioc"
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuResult,
|
||||
ContextMenuService,
|
||||
ContextMenuState,
|
||||
} from "../"
|
||||
import { markRaw, ref } from "vue"
|
||||
import IconArrowDownRight from "~icons/lucide/arrow-down-right"
|
||||
import { currentActiveTab } from "~/helpers/rest/tab"
|
||||
import { getI18n } from "~/modules/i18n"
|
||||
|
||||
//regex containing both url and parameter
|
||||
const urlAndParameterRegex = new RegExp("[^&?]*?=[^&?]*")
|
||||
|
||||
interface Param {
|
||||
[key: string]: string
|
||||
}
|
||||
|
||||
/**
|
||||
* The extracted parameters from the input
|
||||
* with the new URL if it was provided
|
||||
*/
|
||||
interface ExtractedParams {
|
||||
params: Param
|
||||
newURL?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* This menu returns a single result that allows the user
|
||||
* to add the selected text as a parameter
|
||||
* if the selected text is a valid URL
|
||||
*/
|
||||
export class ParameterMenuService extends Service implements ContextMenu {
|
||||
public static readonly ID = "PARAMETER_CONTEXT_MENU_SERVICE"
|
||||
|
||||
private t = getI18n()
|
||||
|
||||
public readonly menuID = "parameter"
|
||||
|
||||
private readonly contextMenu = this.bind(ContextMenuService)
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
|
||||
this.contextMenu.registerMenu(this)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param input The input to extract the parameters from
|
||||
* @returns The extracted parameters and the new URL if it was provided
|
||||
*/
|
||||
private extractParams(input: string): ExtractedParams {
|
||||
let text = input
|
||||
let newURL: string | undefined
|
||||
|
||||
// if the input is a URL, extract the parameters
|
||||
if (text.startsWith("http")) {
|
||||
const url = new URL(text)
|
||||
newURL = url.origin + url.pathname
|
||||
text = url.search.slice(1)
|
||||
}
|
||||
|
||||
const regex = /(\w+)=(\w+)/g
|
||||
const matches = text.matchAll(regex)
|
||||
const params: Param = {}
|
||||
|
||||
// extract the parameters from the input
|
||||
for (const match of matches) {
|
||||
const [, key, value] = match
|
||||
params[key] = value
|
||||
}
|
||||
|
||||
return { params, newURL }
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the parameters from the input to the current request
|
||||
* parameters and updates the endpoint if a new URL was provided
|
||||
* @param text The input to extract the parameters from
|
||||
*/
|
||||
private addParameter(text: string) {
|
||||
const { params, newURL } = this.extractParams(text)
|
||||
|
||||
const queryParams = []
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
queryParams.push({ key, value, active: true })
|
||||
}
|
||||
|
||||
// add the parameters to the current request parameters
|
||||
currentActiveTab.value.document.request.params = [
|
||||
...currentActiveTab.value.document.request.params,
|
||||
...queryParams,
|
||||
]
|
||||
|
||||
if (newURL) {
|
||||
currentActiveTab.value.document.request.endpoint = newURL
|
||||
} else {
|
||||
// remove the parameter from the URL
|
||||
const textRegex = new RegExp(`\\b${text.replace(/\?/g, "")}\\b`, "gi")
|
||||
const sanitizedWord = currentActiveTab.value.document.request.endpoint
|
||||
const newURL = sanitizedWord.replace(textRegex, "")
|
||||
currentActiveTab.value.document.request.endpoint = newURL
|
||||
}
|
||||
}
|
||||
|
||||
getMenuFor(text: Readonly<string>): ContextMenuState {
|
||||
const results = ref<ContextMenuResult[]>([])
|
||||
|
||||
if (urlAndParameterRegex.test(text)) {
|
||||
results.value = [
|
||||
{
|
||||
id: "environment",
|
||||
text: {
|
||||
type: "text",
|
||||
text: this.t("context_menu.add_parameter"),
|
||||
},
|
||||
icon: markRaw(IconArrowDownRight),
|
||||
action: () => {
|
||||
this.addParameter(text)
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
const resultObj = <ContextMenuState>{
|
||||
results: results.value,
|
||||
}
|
||||
|
||||
return resultObj
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import { Service } from "dioc"
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuResult,
|
||||
ContextMenuService,
|
||||
ContextMenuState,
|
||||
} from ".."
|
||||
import { markRaw, ref } from "vue"
|
||||
import IconCopyPlus from "~icons/lucide/copy-plus"
|
||||
import { createNewTab } from "~/helpers/rest/tab"
|
||||
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
||||
import { getI18n } from "~/modules/i18n"
|
||||
|
||||
/**
|
||||
* Used to check if a string is a valid URL
|
||||
* @param url The string to check
|
||||
* @returns Whether the string is a valid URL
|
||||
*/
|
||||
function isValidURL(url: string) {
|
||||
try {
|
||||
// Try to create a URL object
|
||||
// this will fail for endpoints like "localhost:3000", ie without a protocol
|
||||
new URL(url)
|
||||
return true
|
||||
} catch (error) {
|
||||
// Fallback to regular expression check
|
||||
const pattern = /^(https?:\/\/)?([\w.-]+)(\.[\w.-]+)+([/?].*)?$/
|
||||
return pattern.test(url)
|
||||
}
|
||||
}
|
||||
|
||||
export class URLMenuService extends Service implements ContextMenu {
|
||||
public static readonly ID = "URL_CONTEXT_MENU_SERVICE"
|
||||
|
||||
private t = getI18n()
|
||||
|
||||
public readonly menuID = "url"
|
||||
|
||||
private readonly contextMenu = this.bind(ContextMenuService)
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
|
||||
this.contextMenu.registerMenu(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a new tab with the provided URL
|
||||
* @param url The URL to open
|
||||
*/
|
||||
private openNewTab(url: string) {
|
||||
//create a new request object
|
||||
const request = {
|
||||
...getDefaultRESTRequest(),
|
||||
endpoint: url,
|
||||
}
|
||||
|
||||
createNewTab({
|
||||
request: request,
|
||||
isDirty: false,
|
||||
})
|
||||
}
|
||||
|
||||
getMenuFor(text: Readonly<string>): ContextMenuState {
|
||||
const results = ref<ContextMenuResult[]>([])
|
||||
|
||||
if (isValidURL(text)) {
|
||||
results.value = [
|
||||
{
|
||||
id: "link-tab",
|
||||
text: {
|
||||
type: "text",
|
||||
text: this.t("context_menu.open_link_in_new_tab"),
|
||||
},
|
||||
icon: markRaw(IconCopyPlus),
|
||||
action: () => {
|
||||
this.openNewTab(text)
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
const resultObj = <ContextMenuState>{
|
||||
results: results.value,
|
||||
}
|
||||
|
||||
return resultObj
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ WORKDIR /usr/src/app
|
||||
RUN npm i -g pnpm
|
||||
|
||||
COPY . .
|
||||
RUN pnpm install
|
||||
RUN pnpm install --force --frozen-lockfile
|
||||
|
||||
WORKDIR /usr/src/app/packages/hoppscotch-selfhost-web/
|
||||
RUN pnpm run build
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "@hoppscotch/selfhost-web",
|
||||
"private": true,
|
||||
"version": "2023.4.7",
|
||||
"version": "2023.4.8",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev:vite": "vite",
|
||||
"dev:gql-codegen": "graphql-codegen --require dotenv/config --config gql-codegen.yml dotenv_config_path=\"../../.env\" --watch",
|
||||
"dev": "pnpm exec npm-run-all -p -l dev:*",
|
||||
"build": "node --max_old_space_size=16384 ./node_modules/vite/bin/vite.js build",
|
||||
"build": "node --max_old_space_size=4096 ./node_modules/vite/bin/vite.js build",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint src --ext .ts,.js,.vue --ignore-path .gitignore .",
|
||||
"lint:ts": "vue-tsc --noEmit",
|
||||
@@ -66,7 +66,7 @@
|
||||
"vite-plugin-html-config": "^1.0.10",
|
||||
"vite-plugin-inspect": "^0.7.4",
|
||||
"vite-plugin-pages": "^0.26.0",
|
||||
"vite-plugin-pages-sitemap": "^1.4.0",
|
||||
"vite-plugin-pages-sitemap": "^1.4.5",
|
||||
"vite-plugin-pwa": "^0.13.1",
|
||||
"vite-plugin-static-copy": "^0.12.0",
|
||||
"vite-plugin-vue-layouts": "^0.7.0",
|
||||
|
||||
@@ -78,16 +78,14 @@ export default defineConfig({
|
||||
routeStyle: "nuxt",
|
||||
dirs: "../hoppscotch-common/src/pages",
|
||||
importMode: "async",
|
||||
onRoutesGenerated(routes) {
|
||||
// HACK: See: https://github.com/jbaubree/vite-plugin-pages-sitemap/issues/173
|
||||
return ((generateSitemap as any).default as typeof generateSitemap)({
|
||||
onRoutesGenerated: (routes) =>
|
||||
generateSitemap({
|
||||
routes,
|
||||
nuxtStyle: true,
|
||||
allowRobots: true,
|
||||
dest: ".sitemap-gen",
|
||||
hostname: ENV.VITE_BASE_URL,
|
||||
})
|
||||
},
|
||||
}),
|
||||
}),
|
||||
StaticCopy({
|
||||
targets: [
|
||||
|
||||
@@ -6,7 +6,7 @@ WORKDIR /usr/src/app
|
||||
RUN npm i -g pnpm
|
||||
|
||||
COPY . .
|
||||
RUN pnpm install
|
||||
RUN pnpm install --force --frozen-lockfile
|
||||
|
||||
WORKDIR /usr/src/app/packages/hoppscotch-sh-admin/
|
||||
RUN pnpm run build
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
@apply after:backface-hidden;
|
||||
@apply selection:bg-accentDark;
|
||||
@apply selection:text-accentContrast;
|
||||
@apply overscroll-none;
|
||||
}
|
||||
|
||||
:root {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "hoppscotch-sh-admin",
|
||||
"private": true,
|
||||
"version": "2023.4.7",
|
||||
"version": "2023.4.8",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "pnpm exec npm-run-all -p -l dev:*",
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
"@esbuild-plugins/node-globals-polyfill": "^0.1.1",
|
||||
"@esbuild-plugins/node-modules-polyfill": "^0.1.4",
|
||||
"@histoire/plugin-vue": "^0.12.4",
|
||||
"@iconify-json/lucide": "^1.1.40",
|
||||
"@iconify-json/lucide": "^1.1.109",
|
||||
"@intlify/vite-plugin-vue-i18n": "^6.0.1",
|
||||
"@rushstack/eslint-patch": "^1.1.4",
|
||||
"@types/lodash-es": "^4.17.6",
|
||||
@@ -71,12 +71,12 @@
|
||||
"unplugin-vue-components": "^0.21.0",
|
||||
"vite": "^3.2.3",
|
||||
"vite-plugin-checker": "^0.5.1",
|
||||
"vite-plugin-dts": "2.0.0-beta.3",
|
||||
"vite-plugin-dts": "3.2.0",
|
||||
"vite-plugin-fonts": "^0.6.0",
|
||||
"vite-plugin-html-config": "^1.0.10",
|
||||
"vite-plugin-inspect": "^0.7.4",
|
||||
"vite-plugin-pages": "^0.26.0",
|
||||
"vite-plugin-pages-sitemap": "^1.4.0",
|
||||
"vite-plugin-pages-sitemap": "^1.4.5",
|
||||
"vite-plugin-pwa": "^0.13.1",
|
||||
"vite-plugin-vue-layouts": "^0.7.0",
|
||||
"vite-plugin-windicss": "^1.8.8",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
@apply after:backface-hidden;
|
||||
@apply selection:bg-accentDark;
|
||||
@apply selection:text-accentContrast;
|
||||
@apply overscroll-none;
|
||||
}
|
||||
|
||||
:root {
|
||||
|
||||
@@ -1,62 +1,55 @@
|
||||
<template>
|
||||
<HoppSmartLink :to="to" :exact="exact" :blank="blank" class="inline-flex items-center justify-center focus:outline-none"
|
||||
<HoppSmartLink
|
||||
:to="to"
|
||||
:exact="exact"
|
||||
:blank="blank"
|
||||
class="inline-flex items-center justify-center focus:outline-none"
|
||||
:class="[
|
||||
color
|
||||
? `text-${color}-500 hover:text-${color}-600 focus-visible:text-${color}-600`
|
||||
: 'hover:text-secondaryDark focus-visible:text-secondaryDark',
|
||||
{ 'opacity-75 cursor-not-allowed': disabled },
|
||||
{ 'flex-row-reverse': reverse },
|
||||
]" :disabled="disabled" tabindex="0">
|
||||
<component :is="icon" v-if="icon" class="svg-icons" :class="label ? (reverse ? 'ml-2' : 'mr-2') : ''" />
|
||||
]"
|
||||
:disabled="disabled"
|
||||
tabindex="0"
|
||||
>
|
||||
<component
|
||||
:is="icon"
|
||||
v-if="icon"
|
||||
class="svg-icons"
|
||||
:class="label ? (reverse ? 'ml-2' : 'mr-2') : ''"
|
||||
/>
|
||||
{{ label }}
|
||||
</HoppSmartLink>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import HoppSmartLink from "./Link.vue";
|
||||
import { Component, defineComponent, PropType } from "vue"
|
||||
<script setup lang="ts">
|
||||
import HoppSmartLink from "./Link.vue"
|
||||
import type { Component } from "vue"
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
HoppSmartLink
|
||||
},
|
||||
props: {
|
||||
to: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
exact: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
blank: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
icon: {
|
||||
type: Object as PropType<Component | null>,
|
||||
default: null,
|
||||
},
|
||||
svg: {
|
||||
type: Object as PropType<Component | null>,
|
||||
default: null,
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
reverse: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
to: string
|
||||
exact: boolean
|
||||
blank: boolean
|
||||
label: string
|
||||
icon: Component | null
|
||||
svg: Component | null
|
||||
color: string
|
||||
disabled: boolean
|
||||
reverse: boolean
|
||||
}>(),
|
||||
{
|
||||
to: "",
|
||||
exact: true,
|
||||
blank: false,
|
||||
label: "",
|
||||
icon: null,
|
||||
svg: null,
|
||||
color: "",
|
||||
disabled: false,
|
||||
reverse: false,
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
class="relative sticky top-0 z-10 flex-shrink-0 overflow-x-auto divide-x divide-dividerLight bg-primaryLight tabs group-tabs"
|
||||
>
|
||||
<div
|
||||
class="flex flex-1 flex-shrink-0 w-0 overflow-x-auto"
|
||||
class="flex flex-1 flex-shrink-0 w-0 overflow-hidden"
|
||||
ref="scrollContainer"
|
||||
>
|
||||
<div
|
||||
@@ -455,6 +455,7 @@ $slider-height: 4px;
|
||||
@apply min-w-0;
|
||||
@apply bg-dividerDark;
|
||||
@apply hover:bg-secondaryLight;
|
||||
@apply active:bg-secondaryLight;
|
||||
|
||||
width: var(--thumb-width);
|
||||
height: $slider-height;
|
||||
@@ -465,6 +466,7 @@ $slider-height: 4px;
|
||||
@apply min-w-0;
|
||||
@apply bg-dividerDark;
|
||||
@apply hover:bg-secondaryLight;
|
||||
@apply active:bg-secondaryLight;
|
||||
|
||||
width: var(--thumb-width);
|
||||
height: $slider-height;
|
||||
|
||||
@@ -11,14 +11,13 @@ export default defineConfig({
|
||||
vue(),
|
||||
dts({
|
||||
insertTypesEntry: true,
|
||||
skipDiagnostics: true,
|
||||
outputDir: ['dist']
|
||||
outDir: ["dist"],
|
||||
}),
|
||||
WindiCSS({
|
||||
root: path.resolve(__dirname),
|
||||
}),
|
||||
Icons({
|
||||
compiler: "vue3"
|
||||
compiler: "vue3",
|
||||
}),
|
||||
VitePluginFonts({
|
||||
google: {
|
||||
@@ -45,6 +44,6 @@ export default defineConfig({
|
||||
exports: "named",
|
||||
},
|
||||
},
|
||||
emptyOutDir: true
|
||||
emptyOutDir: true,
|
||||
},
|
||||
})
|
||||
|
||||
263
pnpm-lock.yaml
generated
263
pnpm-lock.yaml
generated
@@ -575,9 +575,6 @@ importers:
|
||||
vue:
|
||||
specifier: ^3.2.25
|
||||
version: 3.2.37
|
||||
vue-github-button:
|
||||
specifier: ^3.0.3
|
||||
version: 3.0.3
|
||||
vue-i18n:
|
||||
specifier: ^9.2.2
|
||||
version: 9.2.2(vue@3.2.37)
|
||||
@@ -637,8 +634,8 @@ importers:
|
||||
specifier: ^3.1.1
|
||||
version: 3.1.1(graphql@15.8.0)
|
||||
'@iconify-json/lucide':
|
||||
specifier: ^1.1.40
|
||||
version: 1.1.40
|
||||
specifier: ^1.1.109
|
||||
version: 1.1.109
|
||||
'@intlify/vite-plugin-vue-i18n':
|
||||
specifier: ^7.0.0
|
||||
version: 7.0.0(vite@3.1.4)(vue-i18n@9.2.2)
|
||||
@@ -748,8 +745,8 @@ importers:
|
||||
specifier: ^0.26.0
|
||||
version: 0.26.0(@vue/compiler-sfc@3.2.39)(vite@3.1.4)
|
||||
vite-plugin-pages-sitemap:
|
||||
specifier: ^1.4.0
|
||||
version: 1.4.0
|
||||
specifier: ^1.4.5
|
||||
version: 1.4.5
|
||||
vite-plugin-pwa:
|
||||
specifier: ^0.13.1
|
||||
version: 0.13.1(vite@3.1.4)(workbox-build@6.5.4)(workbox-window@6.5.4)
|
||||
@@ -985,8 +982,8 @@ importers:
|
||||
specifier: ^0.26.0
|
||||
version: 0.26.0(@vue/compiler-sfc@3.2.45)(vite@3.2.4)
|
||||
vite-plugin-pages-sitemap:
|
||||
specifier: ^1.4.0
|
||||
version: 1.4.0
|
||||
specifier: ^1.4.5
|
||||
version: 1.4.5
|
||||
vite-plugin-pwa:
|
||||
specifier: ^0.13.1
|
||||
version: 0.13.1(vite@3.2.4)(workbox-build@6.5.4)(workbox-window@6.5.4)
|
||||
@@ -1239,8 +1236,8 @@ importers:
|
||||
specifier: ^0.12.4
|
||||
version: 0.12.4(histoire@0.12.4)(vite@3.2.4)(vue@3.2.45)
|
||||
'@iconify-json/lucide':
|
||||
specifier: ^1.1.40
|
||||
version: 1.1.40
|
||||
specifier: ^1.1.109
|
||||
version: 1.1.109
|
||||
'@intlify/vite-plugin-vue-i18n':
|
||||
specifier: ^6.0.1
|
||||
version: 6.0.1(vite@3.2.4)
|
||||
@@ -1311,8 +1308,8 @@ importers:
|
||||
specifier: ^0.5.1
|
||||
version: 0.5.1(eslint@8.29.0)(typescript@4.9.3)(vite@3.2.4)
|
||||
vite-plugin-dts:
|
||||
specifier: 2.0.0-beta.3
|
||||
version: 2.0.0-beta.3(@types/node@17.0.45)(rollup@2.79.1)(vite@3.2.4)
|
||||
specifier: 3.2.0
|
||||
version: 3.2.0(@types/node@17.0.45)(sass@1.53.0)(terser@5.14.1)(typescript@4.9.3)
|
||||
vite-plugin-fonts:
|
||||
specifier: ^0.6.0
|
||||
version: 0.6.0(vite@3.2.4)
|
||||
@@ -1326,8 +1323,8 @@ importers:
|
||||
specifier: ^0.26.0
|
||||
version: 0.26.0(@vue/compiler-sfc@3.2.45)(vite@3.2.4)
|
||||
vite-plugin-pages-sitemap:
|
||||
specifier: ^1.4.0
|
||||
version: 1.4.0
|
||||
specifier: ^1.4.5
|
||||
version: 1.4.5
|
||||
vite-plugin-pwa:
|
||||
specifier: ^0.13.1
|
||||
version: 0.13.1(vite@3.2.4)(workbox-build@6.5.4)(workbox-window@6.5.4)
|
||||
@@ -2014,14 +2011,6 @@ packages:
|
||||
dependencies:
|
||||
'@babel/types': 7.18.7
|
||||
|
||||
/@babel/parser@7.20.15:
|
||||
resolution: {integrity: sha512-DI4a1oZuf8wC+oAJA9RW6ga3Zbe8RZFt7kD9i4qAspz3I/yHet1VvC3DiSy/fsUvv5pvJuNPh0LPOdCcqinDPg==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@babel/types': 7.20.7
|
||||
dev: true
|
||||
|
||||
/@babel/parser@7.22.5:
|
||||
resolution: {integrity: sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
@@ -2942,7 +2931,7 @@ packages:
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.18.6
|
||||
'@babel/parser': 7.20.15
|
||||
'@babel/parser': 7.22.5
|
||||
'@babel/types': 7.20.7
|
||||
dev: true
|
||||
|
||||
@@ -5786,10 +5775,10 @@ packages:
|
||||
/@iarna/toml@2.2.5:
|
||||
resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==}
|
||||
|
||||
/@iconify-json/lucide@1.1.40:
|
||||
resolution: {integrity: sha512-4GeQtaiv3mJ+b0sn/c2KL8Tgf4XQvsX1AHDOseuGRhgoLCWG+ZdNRFxF5sp1I6T/VcQccegLPOp5XHn3NC1mmA==}
|
||||
/@iconify-json/lucide@1.1.109:
|
||||
resolution: {integrity: sha512-1+zYieiKUAjN1x66kvcRmmtgBJaDbD7i4To8mhB6+3bEm/i61un76nspJ45LOSGovzBMvYZFIJpqJrGMipWPzw==}
|
||||
dependencies:
|
||||
'@iconify/types': 1.1.0
|
||||
'@iconify/types': 2.0.0
|
||||
dev: true
|
||||
|
||||
/@iconify/types@1.1.0:
|
||||
@@ -5836,8 +5825,8 @@ packages:
|
||||
vue-i18n:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@intlify/message-compiler': 9.3.0-beta.24
|
||||
'@intlify/shared': 9.3.0-beta.24
|
||||
'@intlify/message-compiler': 9.3.0-beta.25
|
||||
'@intlify/shared': 9.3.0-beta.25
|
||||
jsonc-eslint-parser: 1.4.1
|
||||
source-map: 0.6.1
|
||||
vue-i18n: 9.2.2(vue@3.2.37)
|
||||
@@ -5898,11 +5887,11 @@ packages:
|
||||
source-map-js: 1.0.2
|
||||
dev: true
|
||||
|
||||
/@intlify/message-compiler@9.3.0-beta.24:
|
||||
resolution: {integrity: sha512-prhHATkgp0mpPqoVgiAtLmUc1JMvs8fMH6w53AVEBn+VF87dLhzanfmWY5FoZWORG51ag54gBDBOoM/VFv3m3A==}
|
||||
/@intlify/message-compiler@9.3.0-beta.25:
|
||||
resolution: {integrity: sha512-uT7ybqKoDEw1XITQYnTYjWgZnpCDmHv9e3D4MmJDqHl2qCm6anzdUXWKHUhqR87Ha9Z8Rl44v40iSI/4NUbppQ==}
|
||||
engines: {node: '>= 16'}
|
||||
dependencies:
|
||||
'@intlify/shared': 9.3.0-beta.24
|
||||
'@intlify/shared': 9.3.0-beta.25
|
||||
source-map-js: 1.0.2
|
||||
dev: true
|
||||
|
||||
@@ -5915,8 +5904,8 @@ packages:
|
||||
engines: {node: '>= 16'}
|
||||
dev: true
|
||||
|
||||
/@intlify/shared@9.3.0-beta.24:
|
||||
resolution: {integrity: sha512-AKxJ8s7eKIQWkNaf4wyyoLRwf4puCuQgjSChlDJm5JBEt6T8HGgnYTJLRXu6LD/JACn3Qwu6hM/XRX1c9yvjmQ==}
|
||||
/@intlify/shared@9.3.0-beta.25:
|
||||
resolution: {integrity: sha512-Zg+ECV9RPdp227tCJOgvPb+S3i651nf4kKHsMojSyWCppVK/4NFuDrBG2lIQSQL6Iq5LKVr5MkezHCW2NBTQRg==}
|
||||
engines: {node: '>= 16'}
|
||||
dev: true
|
||||
|
||||
@@ -5936,7 +5925,7 @@ packages:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@intlify/bundle-utils': 7.0.0
|
||||
'@intlify/shared': 9.3.0-beta.24
|
||||
'@intlify/shared': 9.3.0-beta.25
|
||||
'@rollup/pluginutils': 4.2.1
|
||||
debug: 4.3.4(supports-color@9.2.2)
|
||||
fast-glob: 3.2.11
|
||||
@@ -5963,7 +5952,7 @@ packages:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@intlify/bundle-utils': 3.4.0(vue-i18n@9.2.2)
|
||||
'@intlify/shared': 9.3.0-beta.24
|
||||
'@intlify/shared': 9.3.0-beta.25
|
||||
'@rollup/pluginutils': 4.2.1
|
||||
debug: 4.3.4(supports-color@9.2.2)
|
||||
fast-glob: 3.2.12
|
||||
@@ -5991,7 +5980,7 @@ packages:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@intlify/bundle-utils': 3.4.0(vue-i18n@9.2.2)
|
||||
'@intlify/shared': 9.3.0-beta.24
|
||||
'@intlify/shared': 9.3.0-beta.25
|
||||
'@rollup/pluginutils': 4.2.1
|
||||
debug: 4.3.4(supports-color@9.2.2)
|
||||
fast-glob: 3.2.12
|
||||
@@ -6623,32 +6612,32 @@ packages:
|
||||
resolution: {integrity: sha512-EWUguj2kd7ldmrF9F+vI5hUOralPd+sdsUnYbRy33vZTuZkduC1shE9TtEMEjAQwyfyMb4ole5KtjF8MsnQOlA==}
|
||||
dev: true
|
||||
|
||||
/@microsoft/api-extractor-model@7.26.4(@types/node@17.0.45):
|
||||
resolution: {integrity: sha512-PDCgCzXDo+SLY5bsfl4bS7hxaeEtnXj7XtuzEE+BtALp7B5mK/NrS2kHWU69pohgsRmEALycQdaQPXoyT2i5MQ==}
|
||||
/@microsoft/api-extractor-model@7.27.4(@types/node@17.0.45):
|
||||
resolution: {integrity: sha512-HjqQFmuGPOS20rtnu+9Jj0QrqZyR59E+piUWXPMZTTn4jaZI+4UmsHSf3Id8vyueAhOBH2cgwBuRTE5R+MfSMw==}
|
||||
dependencies:
|
||||
'@microsoft/tsdoc': 0.14.2
|
||||
'@microsoft/tsdoc-config': 0.16.2
|
||||
'@rushstack/node-core-library': 3.55.2(@types/node@17.0.45)
|
||||
'@rushstack/node-core-library': 3.59.5(@types/node@17.0.45)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
dev: true
|
||||
|
||||
/@microsoft/api-extractor@7.34.4(@types/node@17.0.45):
|
||||
resolution: {integrity: sha512-HOdcci2nT40ejhwPC3Xja9G+WSJmWhCUKKryRfQYsmE9cD+pxmBaKBKCbuS9jUcl6bLLb4Gz+h7xEN5r0QiXnQ==}
|
||||
/@microsoft/api-extractor@7.36.1(@types/node@17.0.45):
|
||||
resolution: {integrity: sha512-2SPp1jq6wDY5IOsRLUv/4FxngslctBZJlztAJ3uWpCAwqKQG7ESdL3DhEza+StbYLtBQmu1Pk6q1Vkhl7qD/bg==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@microsoft/api-extractor-model': 7.26.4(@types/node@17.0.45)
|
||||
'@microsoft/api-extractor-model': 7.27.4(@types/node@17.0.45)
|
||||
'@microsoft/tsdoc': 0.14.2
|
||||
'@microsoft/tsdoc-config': 0.16.2
|
||||
'@rushstack/node-core-library': 3.55.2(@types/node@17.0.45)
|
||||
'@rushstack/rig-package': 0.3.18
|
||||
'@rushstack/ts-command-line': 4.13.2
|
||||
'@rushstack/node-core-library': 3.59.5(@types/node@17.0.45)
|
||||
'@rushstack/rig-package': 0.4.0
|
||||
'@rushstack/ts-command-line': 4.15.1
|
||||
colors: 1.2.5
|
||||
lodash: 4.17.21
|
||||
resolve: 1.22.1
|
||||
semver: 7.3.8
|
||||
source-map: 0.6.1
|
||||
typescript: 4.8.4
|
||||
typescript: 5.0.4
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
dev: true
|
||||
@@ -7228,8 +7217,8 @@ packages:
|
||||
resolution: {integrity: sha512-LwzQKA4vzIct1zNZzBmRKI9QuNpLgTQMEjsQLf3BXuGYb3QPTP4Yjf6mkdX+X1mYttZ808QpOwAzZjv28kq7DA==}
|
||||
dev: true
|
||||
|
||||
/@rushstack/node-core-library@3.55.2(@types/node@17.0.45):
|
||||
resolution: {integrity: sha512-SaLe/x/Q/uBVdNFK5V1xXvsVps0y7h1sN7aSJllQyFbugyOaxhNRF25bwEDnicARNEjJw0pk0lYnJQ9Kr6ev0A==}
|
||||
/@rushstack/node-core-library@3.59.5(@types/node@17.0.45):
|
||||
resolution: {integrity: sha512-1IpV7LufrI1EoVO8hYsb3t6L8L+yp40Sa0OaOV2CIu1zx4e6ZeVNaVIEXFgMXBKdGXkAh21MnCaIzlDNpG6ZQw==}
|
||||
peerDependencies:
|
||||
'@types/node': '*'
|
||||
peerDependenciesMeta:
|
||||
@@ -7246,15 +7235,15 @@ packages:
|
||||
z-schema: 5.0.5
|
||||
dev: true
|
||||
|
||||
/@rushstack/rig-package@0.3.18:
|
||||
resolution: {integrity: sha512-SGEwNTwNq9bI3pkdd01yCaH+gAsHqs0uxfGvtw9b0LJXH52qooWXnrFTRRLG1aL9pf+M2CARdrA9HLHJys3jiQ==}
|
||||
/@rushstack/rig-package@0.4.0:
|
||||
resolution: {integrity: sha512-FnM1TQLJYwSiurP6aYSnansprK5l8WUK8VG38CmAaZs29ZeL1msjK0AP1VS4ejD33G0kE/2cpsPsS9jDenBMxw==}
|
||||
dependencies:
|
||||
resolve: 1.22.1
|
||||
strip-json-comments: 3.1.1
|
||||
dev: true
|
||||
|
||||
/@rushstack/ts-command-line@4.13.2:
|
||||
resolution: {integrity: sha512-bCU8qoL9HyWiciltfzg7GqdfODUeda/JpI0602kbN5YH22rzTxyqYvv7aRLENCM7XCQ1VRs7nMkEqgJUOU8Sag==}
|
||||
/@rushstack/ts-command-line@4.15.1:
|
||||
resolution: {integrity: sha512-EL4jxZe5fhb1uVL/P/wQO+Z8Rc8FMiWJ1G7VgnPDvdIt5GVjRfK7vwzder1CZQiX3x0PY6uxENYLNGTFd1InRQ==}
|
||||
dependencies:
|
||||
'@types/argparse': 1.0.38
|
||||
argparse: 1.0.10
|
||||
@@ -7516,15 +7505,6 @@ packages:
|
||||
engines: {node: '>= 10'}
|
||||
dev: true
|
||||
|
||||
/@ts-morph/common@0.18.1:
|
||||
resolution: {integrity: sha512-RVE+zSRICWRsfrkAw5qCAK+4ZH9kwEFv5h0+/YeHTLieWP7F4wWq4JsKFuNWG+fYh/KF+8rAtgdj5zb2mm+DVA==}
|
||||
dependencies:
|
||||
fast-glob: 3.2.12
|
||||
minimatch: 5.1.0
|
||||
mkdirp: 1.0.4
|
||||
path-browserify: 1.0.1
|
||||
dev: true
|
||||
|
||||
/@tsconfig/node10@1.0.9:
|
||||
resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==}
|
||||
|
||||
@@ -8736,6 +8716,12 @@ packages:
|
||||
muggle-string: 0.1.0
|
||||
dev: true
|
||||
|
||||
/@volar/language-core@1.8.0:
|
||||
resolution: {integrity: sha512-ZHTvZPM3pEbOOuaq+ybNz5TQlHUqPQPK0G1+SonvApGq0e3qgGijjhtL5T7hsCtUEmxfix8FrAuCH14tMBOhTg==}
|
||||
dependencies:
|
||||
'@volar/source-map': 1.8.0
|
||||
dev: true
|
||||
|
||||
/@volar/shared@0.27.24:
|
||||
resolution: {integrity: sha512-Mi8a4GQaiorfb+o4EqOXDZm9E/uBJXgScFgF+NhtcMBOUKHNMKQyLI7YRGumtyJTTdaX7nSDJjGGTkv23tcOtQ==}
|
||||
dependencies:
|
||||
@@ -8760,6 +8746,12 @@ packages:
|
||||
muggle-string: 0.1.0
|
||||
dev: true
|
||||
|
||||
/@volar/source-map@1.8.0:
|
||||
resolution: {integrity: sha512-d35aV0yFkIrkynRSKgrN5hgbMv6ekkFvcJsJGmOZ8UEjqLStto9zq7RSvpp6/PZ7/pa4Gn1f6K1qDt0bq0oUew==}
|
||||
dependencies:
|
||||
muggle-string: 0.3.1
|
||||
dev: true
|
||||
|
||||
/@volar/transforms@0.27.24:
|
||||
resolution: {integrity: sha512-sOHi1ZSapFlxn7yPl4MO5TXd9aWC0BVq2CgXAJ2EESb+ddh2uJbGQgLLNocX+MDh419cUuuFT2QAJpuWHhJcng==}
|
||||
dependencies:
|
||||
@@ -8773,6 +8765,12 @@ packages:
|
||||
'@volar/language-core': 1.0.9
|
||||
dev: true
|
||||
|
||||
/@volar/typescript@1.8.0:
|
||||
resolution: {integrity: sha512-T/U1XLLhXv6tNr40Awznfc6QZWizSL99t6M0DeXtIMbnvSCqjjCVRnwlsq+DK9C1RlO3k8+i0Z8iJn7O1GGtoA==}
|
||||
dependencies:
|
||||
'@volar/language-core': 1.8.0
|
||||
dev: true
|
||||
|
||||
/@volar/vue-code-gen@0.38.2:
|
||||
resolution: {integrity: sha512-whLunD6phSGWBUHZKdTxeglrpzQu26ii8CRVapFdjfyMaVhQ7ESNeIAhkTVyg2ovOPc0PiDYPQEPzfWAADIWog==}
|
||||
dependencies:
|
||||
@@ -8994,6 +8992,25 @@ packages:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@vue/language-core@1.8.4(typescript@4.9.3):
|
||||
resolution: {integrity: sha512-pnNtNcJVfkGYluW0vsVO+Y1gyX+eA0voaS7+1JOhCp5zKeCaL/PAmGYOgfvwML62neL+2H8pnhY7sffmrGpEhw==}
|
||||
peerDependencies:
|
||||
typescript: '*'
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@volar/language-core': 1.8.0
|
||||
'@volar/source-map': 1.8.0
|
||||
'@vue/compiler-dom': 3.3.4
|
||||
'@vue/reactivity': 3.3.4
|
||||
'@vue/shared': 3.3.4
|
||||
minimatch: 9.0.3
|
||||
muggle-string: 0.3.1
|
||||
typescript: 4.9.3
|
||||
vue-template-compiler: 2.7.14
|
||||
dev: true
|
||||
|
||||
/@vue/reactivity-transform@3.2.37:
|
||||
resolution: {integrity: sha512-IWopkKEb+8qpu/1eMKVeXrK0NLw9HicGviJzhJDEyfxTR9e1WtpnnbYkJWurX6WwoFP0sz10xQg8yL8lgskAZg==}
|
||||
dependencies:
|
||||
@@ -9037,6 +9054,12 @@ packages:
|
||||
dependencies:
|
||||
'@vue/shared': 3.2.45
|
||||
|
||||
/@vue/reactivity@3.3.4:
|
||||
resolution: {integrity: sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==}
|
||||
dependencies:
|
||||
'@vue/shared': 3.3.4
|
||||
dev: true
|
||||
|
||||
/@vue/runtime-core@3.2.37:
|
||||
resolution: {integrity: sha512-JPcd9kFyEdXLl/i0ClS7lwgcs0QpUAWj+SKX2ZC3ANKi1U4DOtiEr6cRqFXsPwY5u1L9fAjkinIdB8Rz3FoYNQ==}
|
||||
dependencies:
|
||||
@@ -9101,6 +9124,15 @@ packages:
|
||||
resolution: {integrity: sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==}
|
||||
dev: true
|
||||
|
||||
/@vue/typescript@1.8.4(typescript@4.9.3):
|
||||
resolution: {integrity: sha512-sioQfIY5xcmEAz+cPLvv6CtzGPtGhIdR0Za87zB8M4mPe4OSsE3MBGkXcslf+EzQgF+fm6Gr1SRMSX8r5ZmzDA==}
|
||||
dependencies:
|
||||
'@volar/typescript': 1.8.0
|
||||
'@vue/language-core': 1.8.4(typescript@4.9.3)
|
||||
transitivePeerDependencies:
|
||||
- typescript
|
||||
dev: true
|
||||
|
||||
/@vueuse/core@8.7.5(vue@3.2.45):
|
||||
resolution: {integrity: sha512-tqgzeZGoZcXzoit4kOGLWJibDMLp0vdm6ZO41SSUQhkhtrPhAg6dbIEPiahhUu6sZAmSYvVrZgEr5aKD51nrLA==}
|
||||
peerDependencies:
|
||||
@@ -9509,7 +9541,7 @@ packages:
|
||||
hasBin: true
|
||||
|
||||
/after@0.8.2:
|
||||
resolution: {integrity: sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=}
|
||||
resolution: {integrity: sha512-QbJ0NTQ/I9DI3uSJA4cbexiwQeRAfjPScqIbSjUDd9TOrcg6pTkdgziesOqxBMBzit8vFCTwrP27t13vFOORRA==}
|
||||
dev: false
|
||||
|
||||
/agent-base@6.0.2:
|
||||
@@ -10121,7 +10153,7 @@ packages:
|
||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
|
||||
/base64-arraybuffer@0.1.4:
|
||||
resolution: {integrity: sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=}
|
||||
resolution: {integrity: sha512-a1eIFi4R9ySrbiMuyTGx5e92uRH5tQY6kArNcFaKBUleIoLjdjBg7Zxm3Mqm3Kmkf27HLR/1fnxX9q8GQ7Iavg==}
|
||||
engines: {node: '>= 0.6.0'}
|
||||
dev: false
|
||||
|
||||
@@ -10672,10 +10704,6 @@ packages:
|
||||
engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
|
||||
dev: true
|
||||
|
||||
/code-block-writer@11.0.3:
|
||||
resolution: {integrity: sha512-NiujjUFB4SwScJq2bwbYUtXbZhBSlY6vYzm++3Q6oC+U+injTqfPYFK8wS9COOmb2lueqp0ZRB4nK1VYeHgNyw==}
|
||||
dev: true
|
||||
|
||||
/collect-v8-coverage@1.0.1:
|
||||
resolution: {integrity: sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==}
|
||||
dev: true
|
||||
@@ -10772,14 +10800,14 @@ packages:
|
||||
dev: true
|
||||
|
||||
/component-bind@1.0.0:
|
||||
resolution: {integrity: sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=}
|
||||
resolution: {integrity: sha512-WZveuKPeKAG9qY+FkYDeADzdHyTYdIboXS59ixDeRJL5ZhxpqUnxSOwop4FQjMsiYm3/Or8cegVbpAHNA7pHxw==}
|
||||
dev: false
|
||||
|
||||
/component-emitter@1.3.0:
|
||||
resolution: {integrity: sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==}
|
||||
|
||||
/component-inherit@0.0.3:
|
||||
resolution: {integrity: sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=}
|
||||
resolution: {integrity: sha512-w+LhYREhatpVqTESyGFg3NlP6Iu0kEKUHETY9GoZP/pQyW4mHFZuFWRUCIqVPZ36ueVLtoOEZaAqbCF2RDndaA==}
|
||||
dev: false
|
||||
|
||||
/concat-map@0.0.1:
|
||||
@@ -14055,7 +14083,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
/has-cors@1.1.0:
|
||||
resolution: {integrity: sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=}
|
||||
resolution: {integrity: sha512-g5VNKdkFuUuVCP9gYfDJHjK2nqdQJ7aDLTnycnc2+RvsOQbuLdF5pm7vuE5J76SEBIQjs4kQY/BWq74JUmjbXA==}
|
||||
dev: false
|
||||
|
||||
/has-flag@3.0.0:
|
||||
@@ -14448,7 +14476,7 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
|
||||
/indexof@0.0.1:
|
||||
resolution: {integrity: sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=}
|
||||
resolution: {integrity: sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg==}
|
||||
dev: false
|
||||
|
||||
/inflight@1.0.6:
|
||||
@@ -16384,6 +16412,10 @@ packages:
|
||||
resolution: {integrity: sha512-ymToLHqL02udwVdbkowNpzjFd6UzozMtshPQKVi5k1EjKRqKqBrOnE9QbLEb0/pV76SAiIT13hdL8R6suc+f3g==}
|
||||
dev: true
|
||||
|
||||
/kolorist@1.8.0:
|
||||
resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==}
|
||||
dev: true
|
||||
|
||||
/leac@0.6.0:
|
||||
resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==}
|
||||
dev: false
|
||||
@@ -16752,13 +16784,6 @@ packages:
|
||||
dependencies:
|
||||
sourcemap-codec: 1.4.8
|
||||
|
||||
/magic-string@0.29.0:
|
||||
resolution: {integrity: sha512-WcfidHrDjMY+eLjlU+8OvwREqHwpgCeKVBUpQ3OhYYuvfaYCUgcbuBzappNzZvg/v8onU3oQj+BYpkOJe9Iw4Q==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.4.14
|
||||
dev: true
|
||||
|
||||
/magic-string@0.30.0:
|
||||
resolution: {integrity: sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -17021,6 +17046,13 @@ packages:
|
||||
dependencies:
|
||||
brace-expansion: 2.0.1
|
||||
|
||||
/minimatch@9.0.3:
|
||||
resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
dependencies:
|
||||
brace-expansion: 2.0.1
|
||||
dev: true
|
||||
|
||||
/minimist-options@4.1.0:
|
||||
resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==}
|
||||
engines: {node: '>= 6'}
|
||||
@@ -17503,6 +17535,10 @@ packages:
|
||||
resolution: {integrity: sha512-Tr1knR3d2mKvvWthlk7202rywKbiOm4rVFLsfAaSIhJ6dt9o47W4S+JMtWhd/PW9Wrdew2/S2fSvhz3E2gkfEg==}
|
||||
dev: true
|
||||
|
||||
/muggle-string@0.3.1:
|
||||
resolution: {integrity: sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==}
|
||||
dev: true
|
||||
|
||||
/multer@1.4.4-lts.1:
|
||||
resolution: {integrity: sha512-WeSGziVj6+Z2/MwQo3GvqzgR+9Uc+qt8SwHKh3gvNPiISKfsMfG4SvCOFYlxxgkXt7yIV2i1yczehm0EOKIxIg==}
|
||||
engines: {node: '>= 6.0.0'}
|
||||
@@ -18107,10 +18143,6 @@ packages:
|
||||
utils-merge: 1.0.1
|
||||
dev: false
|
||||
|
||||
/path-browserify@1.0.1:
|
||||
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
|
||||
dev: true
|
||||
|
||||
/path-case@3.0.4:
|
||||
resolution: {integrity: sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==}
|
||||
dependencies:
|
||||
@@ -20292,7 +20324,7 @@ packages:
|
||||
dev: true
|
||||
|
||||
/to-array@0.1.4:
|
||||
resolution: {integrity: sha1-F+bBH3PdTz10zaek/zI46a2b+JA=}
|
||||
resolution: {integrity: sha512-LhVdShQD/4Mk4zXNroIQZJC+Ap3zgLcDuwEdcmLv9CCO73NWockQDwyUnW/m8VX/EElfL6FcYx7EeutN4HJA6A==}
|
||||
dev: false
|
||||
|
||||
/to-fast-properties@2.0.0:
|
||||
@@ -20473,13 +20505,6 @@ packages:
|
||||
resolution: {integrity: sha512-DEQrfv6l7IvN2jlzc/VTdZJYsWUnQNCsueYjMkC/iXoEoi5fNan6MjeDqkvhfzbmHgdz9UxDUluX3V5HdjTydQ==}
|
||||
dev: true
|
||||
|
||||
/ts-morph@17.0.1:
|
||||
resolution: {integrity: sha512-10PkHyXmrtsTvZSL+cqtJLTgFXkU43Gd0JCc0Rw6GchWbqKe0Rwgt1v3ouobTZwQzF1mGhDeAlWYBMGRV7y+3g==}
|
||||
dependencies:
|
||||
'@ts-morph/common': 0.18.1
|
||||
code-block-writer: 11.0.3
|
||||
dev: true
|
||||
|
||||
/ts-node-dev@2.0.0(@types/node@18.16.17)(typescript@4.9.3):
|
||||
resolution: {integrity: sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==}
|
||||
engines: {node: '>=0.8.0'}
|
||||
@@ -20818,6 +20843,12 @@ packages:
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/typescript@5.0.4:
|
||||
resolution: {integrity: sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==}
|
||||
engines: {node: '>=12.20'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/ua-parser-js@0.7.31:
|
||||
resolution: {integrity: sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==}
|
||||
dev: true
|
||||
@@ -21372,27 +21403,31 @@ packages:
|
||||
vscode-uri: 3.0.3
|
||||
dev: true
|
||||
|
||||
/vite-plugin-dts@2.0.0-beta.3(@types/node@17.0.45)(rollup@2.79.1)(vite@3.2.4):
|
||||
resolution: {integrity: sha512-QrsbTxyt0choSYXPxPfmN9XcSvxcVZk0zticxLrI5DkECs9KhDrSVGok1YP/UPkoKpfF9ThtOJcM5Rjuesxv/w==}
|
||||
/vite-plugin-dts@3.2.0(@types/node@17.0.45)(sass@1.53.0)(terser@5.14.1)(typescript@4.9.3):
|
||||
resolution: {integrity: sha512-s+dwJvDcb/AWgb49oVbq9JiUSIMwaVpFfV4SVIaBZmv9OZyeyDGxujaq+z4HJ4LB4hUG5c4oRAJyLfV66c763Q==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
peerDependencies:
|
||||
vite: '>=2.9.0'
|
||||
typescript: '*'
|
||||
dependencies:
|
||||
'@babel/parser': 7.20.15
|
||||
'@microsoft/api-extractor': 7.34.4(@types/node@17.0.45)
|
||||
'@microsoft/api-extractor': 7.36.1(@types/node@17.0.45)
|
||||
'@rollup/pluginutils': 5.0.2(rollup@2.79.1)
|
||||
'@rushstack/node-core-library': 3.55.2(@types/node@17.0.45)
|
||||
'@rushstack/node-core-library': 3.59.5(@types/node@17.0.45)
|
||||
'@vue/language-core': 1.8.4(typescript@4.9.3)
|
||||
debug: 4.3.4(supports-color@9.2.2)
|
||||
fast-glob: 3.2.12
|
||||
fs-extra: 10.1.0
|
||||
kolorist: 1.7.0
|
||||
magic-string: 0.29.0
|
||||
ts-morph: 17.0.1
|
||||
kolorist: 1.8.0
|
||||
typescript: 4.9.3
|
||||
vue-tsc: 1.8.4(typescript@4.9.3)
|
||||
optionalDependencies:
|
||||
rollup: 2.79.1
|
||||
vite: 3.2.4(@types/node@17.0.45)(sass@1.53.0)(terser@5.14.1)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- rollup
|
||||
- less
|
||||
- sass
|
||||
- stylus
|
||||
- sugarss
|
||||
- supports-color
|
||||
- terser
|
||||
dev: true
|
||||
|
||||
/vite-plugin-eslint@1.8.1(eslint@8.29.0)(vite@3.2.4):
|
||||
@@ -21478,8 +21513,8 @@ packages:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/vite-plugin-pages-sitemap@1.4.0:
|
||||
resolution: {integrity: sha512-8GlmNSeObvDVUdmgutfWnGBZGsn3Zb8IXCjRODFB0tOhQqAQPZRwXrFha6ZLjvF4DgVJI3sW2FfiXa1wFxJvmg==}
|
||||
/vite-plugin-pages-sitemap@1.4.5:
|
||||
resolution: {integrity: sha512-AcEoJ+0D0P1CwR1LjzBznHs6yNQVP7ha7l6cl/VHrHFcQXbVyKc+QOmLi1a3eONy+aDA3K01pZVK8TzTIufq/w==}
|
||||
dev: true
|
||||
|
||||
/vite-plugin-pages@0.26.0(@vue/compiler-sfc@3.2.39)(vite@3.1.4):
|
||||
@@ -22307,6 +22342,18 @@ packages:
|
||||
typescript: 4.9.3
|
||||
dev: true
|
||||
|
||||
/vue-tsc@1.8.4(typescript@4.9.3):
|
||||
resolution: {integrity: sha512-+hgpOhIx11vbi8/AxEdaPj3fiRwN9wy78LpsNNw2V995/IWa6TMyQxHbaw2ZKUpdwjySSHgrT6ohDEhUgFxGYw==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
typescript: '*'
|
||||
dependencies:
|
||||
'@vue/language-core': 1.8.4(typescript@4.9.3)
|
||||
'@vue/typescript': 1.8.4(typescript@4.9.3)
|
||||
semver: 7.3.8
|
||||
typescript: 4.9.3
|
||||
dev: true
|
||||
|
||||
/vue@2.7.1:
|
||||
resolution: {integrity: sha512-X1YkFddhbTAU2FPK0gBZ/vDOcOMA8ZT4uHoFVor1bUb7BpVGdEswS286YGtODsf/Ghfr1LM1sBMFAY8XT+dVhA==}
|
||||
dependencies:
|
||||
@@ -23115,7 +23162,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
/yeast@0.1.2:
|
||||
resolution: {integrity: sha1-AI4G2AlDIMNy28L47XagymyKxBk=}
|
||||
resolution: {integrity: sha512-8HFIh676uyGYP6wP13R/j6OJ/1HwJ46snpvzE7aHAN3Ryqh2yX6Xox2B4CUmTwwOIzlG3Bs7ocsP5dZH/R1Qbg==}
|
||||
dev: false
|
||||
|
||||
/yn@3.1.1:
|
||||
@@ -23154,3 +23201,7 @@ packages:
|
||||
optionalDependencies:
|
||||
commander: 9.5.0
|
||||
dev: true
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
Reference in New Issue
Block a user