Compare commits

..

1 Commits

Author SHA1 Message Date
Gregory Schier
4f9a7e9c88 2024.5.0 (#39) 2024-06-03 14:08:24 -07:00
135 changed files with 6408 additions and 4551 deletions

View File

@@ -1,18 +0,0 @@
on:
pull_request:
branches: [develop]
name: CI (JS)
jobs:
test:
name: Lint/Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
- run: npm ci
- run: npm run lint
- run: npm test

View File

@@ -1,36 +0,0 @@
on:
pull_request:
branches: [develop]
paths:
- src-tauri/**
- .github/workflows/**
name: CI (Rust)
defaults:
run:
working-directory: src-tauri
jobs:
test:
name: Check/Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev
- uses: dtolnay/rust-toolchain@stable
- uses: actions/cache@v3
continue-on-error: false
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-
- run: cargo check --all
- run: cargo test --all

View File

@@ -1,8 +1,9 @@
name: Generate Artifacts
on:
push:
tags: [ v* ]
branches:
- release
- beta
jobs:
build-artifacts:
permissions:
@@ -32,17 +33,6 @@ jobs:
with:
# Those targets are only used on macos runners so it's in an `if` to slightly speed up windows and linux builds.
targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
- uses: actions/cache@v3
continue-on-error: false
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-
- name: install dependencies (ubuntu only)
if: matrix.platform == 'ubuntu-22.04' # This must match the platform value defined above.
run: |

3
.gitignore vendored
View File

@@ -27,5 +27,4 @@ dist-ssr
*.sqlite
*.sqlite-*
.cargo
plugins/**/build
.cargo

1
.tauriignore Normal file
View File

@@ -0,0 +1 @@
plugins

Binary file not shown.

784
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
{
"name": "yaak",
"name": "yaak-app",
"private": true,
"version": "0.0.0",
"type": "module",
@@ -52,7 +52,6 @@
"codemirror": "^6.0.1",
"codemirror-json-schema": "^0.6.1",
"date-fns": "^3.3.1",
"fast-fuzzy": "^1.12.0",
"focus-trap-react": "^10.1.1",
"format-graphql": "^1.4.0",
"framer-motion": "^9.0.4",
@@ -65,7 +64,6 @@
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18.2.0",
"react-helmet-async": "^1.3.0",
"react-pdf": "^9.0.0",
"react-router-dom": "^6.8.1",
"react-use": "^17.4.0",
"slugify": "^1.6.6",
@@ -76,7 +74,7 @@
"devDependencies": {
"@tailwindcss/nesting": "^0.0.0-insiders.565cd3e",
"@tanstack/react-query-devtools": "^5.35.5",
"@tauri-apps/cli": ">=2.0.0-beta.0",
"@tauri-apps/cli": "^2.0.0-beta.15",
"@types/node": "^18.7.10",
"@types/papaparse": "^5.3.7",
"@types/parse-color": "^1.0.1",
@@ -105,7 +103,6 @@
"tailwindcss": "^3.2.7",
"typescript": "^5.4.5",
"vite": "^5.0.0",
"vite-plugin-static-copy": "^1.0.5",
"vite-plugin-svgr": "^4.2.0",
"vite-plugin-top-level-await": "^1.4.1",
"vitest": "^1.3.0"

View File

@@ -2,7 +2,7 @@ import { HttpRequest } from '../../../src-web/lib/models';
const NEWLINE = '\\\n ';
export function pluginHookExport(_: any, request: Partial<HttpRequest>) {
export function pluginHookExport(request: Partial<HttpRequest>) {
const xs = ['curl'];
// Add method and URL all on first line

View File

@@ -1,12 +1,10 @@
import { describe, expect, test } from 'vitest';
import { pluginHookExport } from '../src';
const ctx = {};
describe('exporter-curl', () => {
test('Exports GET with params', () => {
expect(
pluginHookExport(ctx, {
pluginHookExport({
url: 'https://yaak.app',
urlParameters: [
{ name: 'a', value: 'aaa' },
@@ -20,7 +18,7 @@ describe('exporter-curl', () => {
});
test('Exports POST with url form data', () => {
expect(
pluginHookExport(ctx, {
pluginHookExport({
url: 'https://yaak.app',
method: 'POST',
bodyType: 'application/x-www-form-urlencoded',
@@ -39,7 +37,7 @@ describe('exporter-curl', () => {
test('Exports PUT with multipart form', () => {
expect(
pluginHookExport(ctx, {
pluginHookExport({
url: 'https://yaak.app',
method: 'PUT',
bodyType: 'multipart/form-data',
@@ -64,7 +62,7 @@ describe('exporter-curl', () => {
test('Exports JSON body', () => {
expect(
pluginHookExport(ctx, {
pluginHookExport({
url: 'https://yaak.app',
method: 'POST',
bodyType: 'application/json',
@@ -84,7 +82,7 @@ describe('exporter-curl', () => {
test('Exports multi-line JSON body', () => {
expect(
pluginHookExport(ctx, {
pluginHookExport({
url: 'https://yaak.app',
method: 'POST',
bodyType: 'application/json',
@@ -104,7 +102,7 @@ describe('exporter-curl', () => {
test('Exports headers', () => {
expect(
pluginHookExport(ctx, {
pluginHookExport({
headers: [
{ name: 'a', value: 'aaa' },
{ name: 'b', value: 'bbb', enabled: true },
@@ -116,7 +114,7 @@ describe('exporter-curl', () => {
test('Basic auth', () => {
expect(
pluginHookExport(ctx, {
pluginHookExport({
url: 'https://yaak.app',
authenticationType: 'basic',
authentication: {
@@ -129,7 +127,7 @@ describe('exporter-curl', () => {
test('Broken basic auth', () => {
expect(
pluginHookExport(ctx, {
pluginHookExport({
url: 'https://yaak.app',
authenticationType: 'basic',
authentication: {},
@@ -139,7 +137,7 @@ describe('exporter-curl', () => {
test('Digest auth', () => {
expect(
pluginHookExport(ctx, {
pluginHookExport({
url: 'https://yaak.app',
authenticationType: 'digest',
authentication: {
@@ -152,7 +150,7 @@ describe('exporter-curl', () => {
test('Bearer auth', () => {
expect(
pluginHookExport(ctx, {
pluginHookExport({
url: 'https://yaak.app',
authenticationType: 'bearer',
authentication: {
@@ -164,7 +162,7 @@ describe('exporter-curl', () => {
test('Broken bearer auth', () => {
expect(
pluginHookExport(ctx, {
pluginHookExport({
url: 'https://yaak.app',
authenticationType: 'bearer',
authentication: {

View File

@@ -8,8 +8,6 @@ export default defineConfig({
fileName: 'index',
formats: ['es'],
},
emptyOutDir: true,
sourcemap: true,
outDir: resolve(__dirname, '../../src-tauri/plugins/exporter-curl'),
},
});

View File

@@ -1,6 +1,6 @@
import jp from 'jsonpath';
export function pluginHookResponseFilter(ctx, filter, text) {
export function pluginHookResponseFilter(filter, text) {
let parsed;
try {
parsed = JSON.parse(text);

View File

@@ -8,8 +8,6 @@ export default defineConfig({
fileName: 'index',
formats: ['es'],
},
emptyOutDir: true,
sourcemap: true,
outDir: resolve(__dirname, '../../src-tauri/plugins/filter-jsonpath'),
},
});

View File

@@ -1,7 +1,7 @@
import xpath from 'xpath';
import { DOMParser } from '@xmldom/xmldom';
export function pluginHookResponseFilter(ctx, filter, text) {
export function pluginHookResponseFilter(filter, text) {
const doc = new DOMParser().parseFromString(text, 'text/xml');
const filtered = `${xpath.select(filter, doc)}`;
return { filtered };

View File

@@ -8,8 +8,6 @@ export default defineConfig({
fileName: 'index',
formats: ['es'],
},
emptyOutDir: true,
sourcemap: true,
outDir: resolve(__dirname, '../../src-tauri/plugins/filter-xpath'),
},
});

View File

@@ -43,7 +43,7 @@ type Pair = string | boolean;
type PairsByName = Record<string, Pair[]>;
export function pluginHookImport(_: any, rawData: string) {
export function pluginHookImport(rawData: string) {
if (!rawData.match(/^\s*curl /)) {
return null;
}

View File

@@ -2,11 +2,9 @@ import { describe, expect, test } from 'vitest';
import { HttpRequest, Model, Workspace } from '../../../src-web/lib/models';
import { pluginHookImport } from '../src';
const ctx = {};
describe('importer-curl', () => {
test('Imports basic GET', () => {
expect(pluginHookImport(ctx, 'curl https://yaak.app')).toEqual({
expect(pluginHookImport('curl https://yaak.app')).toEqual({
resources: {
workspaces: [baseWorkspace()],
httpRequests: [
@@ -19,7 +17,7 @@ describe('importer-curl', () => {
});
test('Explicit URL', () => {
expect(pluginHookImport(ctx, 'curl --url https://yaak.app')).toEqual({
expect(pluginHookImport('curl --url https://yaak.app')).toEqual({
resources: {
workspaces: [baseWorkspace()],
httpRequests: [
@@ -32,7 +30,7 @@ describe('importer-curl', () => {
});
test('Missing URL', () => {
expect(pluginHookImport(ctx, 'curl -X POST')).toEqual({
expect(pluginHookImport('curl -X POST')).toEqual({
resources: {
workspaces: [baseWorkspace()],
httpRequests: [
@@ -45,7 +43,7 @@ describe('importer-curl', () => {
});
test('URL between', () => {
expect(pluginHookImport(ctx, 'curl -v https://yaak.app -X POST')).toEqual({
expect(pluginHookImport('curl -v https://yaak.app -X POST')).toEqual({
resources: {
workspaces: [baseWorkspace()],
httpRequests: [
@@ -59,7 +57,7 @@ describe('importer-curl', () => {
});
test('Random flags', () => {
expect(pluginHookImport(ctx, 'curl --random -Z -Y -S --foo https://yaak.app')).toEqual({
expect(pluginHookImport('curl --random -Z -Y -S --foo https://yaak.app')).toEqual({
resources: {
workspaces: [baseWorkspace()],
httpRequests: [
@@ -72,7 +70,7 @@ describe('importer-curl', () => {
});
test('Imports --request method', () => {
expect(pluginHookImport(ctx, 'curl --request POST https://yaak.app')).toEqual({
expect(pluginHookImport('curl --request POST https://yaak.app')).toEqual({
resources: {
workspaces: [baseWorkspace()],
httpRequests: [
@@ -86,7 +84,7 @@ describe('importer-curl', () => {
});
test('Imports -XPOST method', () => {
expect(pluginHookImport(ctx, 'curl -XPOST --request POST https://yaak.app')).toEqual({
expect(pluginHookImport('curl -XPOST --request POST https://yaak.app')).toEqual({
resources: {
workspaces: [baseWorkspace()],
httpRequests: [
@@ -101,10 +99,7 @@ describe('importer-curl', () => {
test('Imports multiple requests', () => {
expect(
pluginHookImport(
ctx,
'curl \\\n https://yaak.app\necho "foo"\ncurl example.com;curl foo.com',
),
pluginHookImport('curl \\\n https://yaak.app\necho "foo"\ncurl example.com;curl foo.com'),
).toEqual({
resources: {
workspaces: [baseWorkspace()],
@@ -119,7 +114,7 @@ describe('importer-curl', () => {
test('Imports form data', () => {
expect(
pluginHookImport(ctx, 'curl -X POST -F "a=aaa" -F b=bbb" -F f=@filepath https://yaak.app'),
pluginHookImport('curl -X POST -F "a=aaa" -F b=bbb" -F f=@filepath https://yaak.app'),
).toEqual({
resources: {
workspaces: [baseWorkspace()],
@@ -149,7 +144,7 @@ describe('importer-curl', () => {
});
test('Imports data params as form url-encoded', () => {
expect(pluginHookImport(ctx, 'curl -d a -d b -d c=ccc https://yaak.app')).toEqual({
expect(pluginHookImport('curl -d a -d b -d c=ccc https://yaak.app')).toEqual({
resources: {
workspaces: [baseWorkspace()],
httpRequests: [
@@ -179,7 +174,7 @@ describe('importer-curl', () => {
test('Imports data params as text', () => {
expect(
pluginHookImport(ctx, 'curl -H Content-Type:text/plain -d a -d b -d c=ccc https://yaak.app'),
pluginHookImport('curl -H Content-Type:text/plain -d a -d b -d c=ccc https://yaak.app'),
).toEqual({
resources: {
workspaces: [baseWorkspace()],
@@ -199,7 +194,6 @@ describe('importer-curl', () => {
test('Imports multi-line JSON', () => {
expect(
pluginHookImport(
ctx,
`curl -H Content-Type:application/json -d $'{\n "foo":"bar"\n}' https://yaak.app`,
),
).toEqual({
@@ -220,7 +214,7 @@ describe('importer-curl', () => {
test('Imports multiple headers', () => {
expect(
pluginHookImport(ctx, 'curl -H Foo:bar --header Name -H AAA:bbb -H :ccc https://yaak.app'),
pluginHookImport('curl -H Foo:bar --header Name -H AAA:bbb -H :ccc https://yaak.app'),
).toEqual({
resources: {
workspaces: [baseWorkspace()],
@@ -240,7 +234,7 @@ describe('importer-curl', () => {
});
test('Imports basic auth', () => {
expect(pluginHookImport(ctx, 'curl --user user:pass https://yaak.app')).toEqual({
expect(pluginHookImport('curl --user user:pass https://yaak.app')).toEqual({
resources: {
workspaces: [baseWorkspace()],
httpRequests: [
@@ -258,7 +252,7 @@ describe('importer-curl', () => {
});
test('Imports digest auth', () => {
expect(pluginHookImport(ctx, 'curl --digest --user user:pass https://yaak.app')).toEqual({
expect(pluginHookImport('curl --digest --user user:pass https://yaak.app')).toEqual({
resources: {
workspaces: [baseWorkspace()],
httpRequests: [
@@ -276,7 +270,7 @@ describe('importer-curl', () => {
});
test('Imports cookie as header', () => {
expect(pluginHookImport(ctx, 'curl --cookie "foo=bar" https://yaak.app')).toEqual({
expect(pluginHookImport('curl --cookie "foo=bar" https://yaak.app')).toEqual({
resources: {
workspaces: [baseWorkspace()],
httpRequests: [
@@ -290,7 +284,7 @@ describe('importer-curl', () => {
});
test('Imports query params from the URL', () => {
expect(pluginHookImport(ctx, 'curl "https://yaak.app?foo=bar&baz=a%20a"')).toEqual({
expect(pluginHookImport('curl "https://yaak.app?foo=bar&baz=a%20a"')).toEqual({
resources: {
workspaces: [baseWorkspace()],
httpRequests: [

View File

@@ -8,8 +8,6 @@ export default defineConfig({
fileName: 'index',
formats: ['es'],
},
emptyOutDir: true,
sourcemap: true,
outDir: resolve(__dirname, '../../src-tauri/plugins/importer-curl'),
},
});

View File

@@ -5,7 +5,7 @@ import {
HttpRequest,
Workspace,
} from '../../../src-web/lib/models';
import '../../../src-web/plugin/runtime.d.ts';
import { parse as parseYaml } from 'yaml';
type AtLeast<T, K extends keyof T> = Partial<T> & Pick<T, K>;
@@ -17,7 +17,7 @@ export interface ExportResources {
folders: AtLeast<Folder, 'name' | 'id' | 'model' | 'workspaceId'>[];
}
export function pluginHookImport(ctx: YaakContext, contents: string) {
export function pluginHookImport(contents: string) {
let parsed: any;
try {
@@ -25,10 +25,8 @@ export function pluginHookImport(ctx: YaakContext, contents: string) {
} catch (e) {}
try {
parsed = parsed ?? YAML.parse(contents);
} catch (e) {
console.log('FAILED', e);
}
parsed = parseYaml(contents);
} catch (e) {}
if (!isJSObject(parsed)) return;
if (!Array.isArray(parsed.resources)) return;

View File

@@ -8,8 +8,6 @@ export default defineConfig({
fileName: 'index',
formats: ['es'],
},
emptyOutDir: true,
sourcemap: true,
outDir: resolve(__dirname, '../../src-tauri/plugins/importer-insomnia'),
},
});

View File

@@ -13,11 +13,7 @@ interface ExportResources {
folders: AtLeast<Folder, 'name' | 'id' | 'model' | 'workspaceId'>[];
}
export function pluginHookImport(
ctx: any,
contents: string,
): { resources: ExportResources } | undefined {
console.log('CTX', ctx);
export function pluginHookImport(contents: string): { resources: ExportResources } | undefined {
const root = parseJSONToRecord(contents);
if (root == null) return;

View File

@@ -23,7 +23,7 @@ describe('importer-postman', () => {
for (const fixture of fixtures) {
test('Imports ' + fixture, () => {
const contents = fs.readFileSync(path.join(p, fixture), 'utf-8');
const imported = pluginHookImport({}, contents);
const imported = pluginHookImport(contents);
const folder0 = newId('folder');
const folder1 = newId('folder');
expect(imported).toEqual({

View File

@@ -8,8 +8,6 @@ export default defineConfig({
fileName: 'index',
formats: ['es'],
},
emptyOutDir: true,
sourcemap: true,
outDir: resolve(__dirname, '../../src-tauri/plugins/importer-postman'),
},
});

View File

@@ -1,4 +1,4 @@
export function pluginHookImport(ctx: any, contents: string) {
export function pluginHookImport(contents: string) {
let parsed;
try {
parsed = JSON.parse(contents);
@@ -18,7 +18,7 @@ export function pluginHookImport(ctx: any, contents: string) {
// Migrate v1 to v2 -- changes requests to httpRequests
if ('requests' in parsed.resources) {
parsed.resources.httpRequests = parsed.resources.requests;
delete parsed.resources['requests'];
delete parsed.resources.requests;
}
return { resources: parsed.resources }; // Should already be in the correct format

View File

@@ -1,18 +1,15 @@
import { describe, expect, test } from 'vitest';
import { pluginHookImport } from '../src';
const ctx = {};
describe('importer-yaak', () => {
test('Skips invalid imports', () => {
expect(pluginHookImport(ctx, 'not JSON')).toBeUndefined();
expect(pluginHookImport(ctx, '[]')).toBeUndefined();
expect(pluginHookImport(ctx, JSON.stringify({ resources: {} }))).toBeUndefined();
expect(pluginHookImport('not JSON')).toBeUndefined();
expect(pluginHookImport('[]')).toBeUndefined();
expect(pluginHookImport(JSON.stringify({ resources: {} }))).toBeUndefined();
});
test('converts schema 1 to 2', () => {
const imported = pluginHookImport(
ctx,
JSON.stringify({
yaakSchema: 1,
resources: {

View File

@@ -8,8 +8,6 @@ export default defineConfig({
fileName: 'index',
formats: ['es'],
},
emptyOutDir: true,
sourcemap: true,
outDir: resolve(__dirname, '../../src-tauri/plugins/importer-yaak'),
},
});

View File

@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
"query": "\n SELECT\n id, model, created_at, updated_at, theme, appearance,\n theme_dark, theme_light, update_channel,\n interface_font_size, interface_scale, editor_font_size, editor_soft_wrap, \n open_workspace_new_window\n FROM settings\n WHERE id = 'default'\n ",
"query": "\n SELECT\n id, model, created_at, updated_at, theme, appearance,\n theme_dark, theme_light, update_channel,\n interface_font_size, interface_scale, editor_font_size, editor_soft_wrap\n FROM settings\n WHERE id = 'default'\n ",
"describe": {
"columns": [
{
@@ -67,11 +67,6 @@
"name": "editor_soft_wrap",
"ordinal": 12,
"type_info": "Bool"
},
{
"name": "open_workspace_new_window",
"ordinal": 13,
"type_info": "Bool"
}
],
"parameters": {
@@ -90,9 +85,8 @@
false,
false,
false,
false,
true
false
]
},
"hash": "05dca7fe15ab1bf03952e94498ef3130e16f752da72782783696eb2cca4736d5"
"hash": "ca3485d87b060cd77c4114d2af544adf18f6f15341d9d5db40865e92a80da4e2"
}

View File

@@ -1,12 +1,12 @@
{
"db_name": "SQLite",
"query": "\n UPDATE settings SET (\n theme, appearance, theme_dark, theme_light, update_channel,\n interface_font_size, interface_scale, editor_font_size, editor_soft_wrap,\n open_workspace_new_window\n ) = (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) WHERE id = 'default';\n ",
"query": "\n UPDATE settings SET (\n theme, appearance, theme_dark, theme_light, update_channel,\n interface_font_size, interface_scale, editor_font_size, editor_soft_wrap\n ) = (?, ?, ?, ?, ?, ?, ?, ?, ?) WHERE id = 'default';\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 10
"Right": 9
},
"nullable": []
},
"hash": "6b5edf45a6799cd7f87c23a3c7f818ad110d58c601f694a619d9345ae9e8e11d"
"hash": "efd8ba41ea909b18dd520c57c1d464c5ae057b720cbbedcaec1513d43535632c"
}

1809
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
workspace = { members = ["grpc", "templates"] }
workspace = { members = ["grpc"] }
[package]
name = "yaak-app"
@@ -8,7 +8,7 @@ edition = "2021"
# Produce a library for mobile support
[lib]
name = "tauri_app_lib"
crate-type = ["staticlib", "cdylib", "lib"]
crate-type = ["staticlib", "cdylib", "rlib"]
[profile.release]
strip = true # Automatically strip symbols from the binary.
@@ -20,39 +20,43 @@ tauri-build = { version = "2.0.0-beta", features = [] }
objc = "0.2.7"
cocoa = "0.25.0"
[target.'cfg(target_os = "windows")'.dependencies]
windows = { version = "0.56.0", features = [
"Win32_Graphics_Dwm",
"Win32_Foundation",
"Win32_UI_Controls",
] }
[target.'cfg(target_os = "linux")'.dependencies]
openssl-sys = { version = "0.9", features = ["vendored"] } # For Ubuntu installation to work
[dependencies]
grpc = { path = "./grpc" }
templates = { path = "./templates" }
anyhow = "1.0.86"
base64 = "0.22.0"
boa_engine = { version = "0.18.0", features = ["annex-b"] }
boa_runtime = { version = "0.18.0" }
chrono = { version = "0.4.31", features = ["serde"] }
datetime = "0.5.2"
deno_ast = { version = "0.39.0", features = ["transpiling"] }
deno_console = "0.155.0"
deno_core = { version = "0.284.0" }
hex_color = "3.0.0"
http = "1"
log = "0.4.21"
http = "0.2.10"
rand = "0.8.5"
regex = "1.10.2"
reqwest = { version = "0.12.4", features = ["multipart", "cookies", "gzip", "brotli", "deflate", "json", "native-tls-alpn"] }
reqwest_cookie_store = "0.8.0"
reqwest = { version = "0.11.23", features = ["multipart", "cookies", "gzip", "brotli", "deflate", "json"] }
serde = { version = "1.0.198", features = ["derive"] }
serde_json = { version = "1.0.116", features = ["raw_value"] }
serde_yaml = "0.9.34"
sqlx = { version = "0.7.4", features = ["sqlite", "runtime-tokio-rustls", "json", "chrono", "time"] }
tauri = { version = "2.0.0-beta", features = ["devtools", "protocol-asset"] }
tauri-plugin-clipboard-manager = "2.1.0-beta"
tauri-plugin-dialog = "2.0.0-beta"
tauri-plugin-fs = "2.0.0-beta"
tauri-plugin-log = { version = "2.0.0-beta", features = ["colored"] }
tauri-plugin-os = "2.0.0-beta"
tauri-plugin-shell = "2.0.0-beta"
tauri-plugin-updater = "2.0.0-beta"
tauri-plugin-window-state = "2.0.0-beta"
tauri = { version = "2.0.0-beta.22", features = ["config-toml", "devtools", "protocol-asset"] }
tauri-plugin-clipboard-manager = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-dialog = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-log = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2", features = ["colored"] }
tauri-plugin-shell = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-os = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-updater = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-fs = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-deep-link = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tokio = { version = "1.36.0", features = ["sync"] }
tokio-stream = "0.1.15"
uuid = "1.7.0"
log = "0.4.21"
datetime = "0.5.2"
reqwest_cookie_store = "0.6.0"
grpc = { path = "./grpc" }
tokio-stream = "0.1.15"
regex = "1.10.2"
hex_color = "3.0.0"

File diff suppressed because one or more lines are too long

View File

@@ -2512,6 +2512,69 @@
"clipboard-manager:deny-write-text"
]
},
{
"description": "deep-link:default -> Allows reading the opened deep link via the get_current command",
"type": "string",
"enum": [
"deep-link:default"
]
},
{
"description": "deep-link:allow-get-current -> Enables the get_current command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:allow-get-current"
]
},
{
"description": "deep-link:allow-is-registered -> Enables the is_registered command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:allow-is-registered"
]
},
{
"description": "deep-link:allow-register -> Enables the register command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:allow-register"
]
},
{
"description": "deep-link:allow-unregister -> Enables the unregister command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:allow-unregister"
]
},
{
"description": "deep-link:deny-get-current -> Denies the get_current command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:deny-get-current"
]
},
{
"description": "deep-link:deny-is-registered -> Denies the is_registered command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:deny-is-registered"
]
},
{
"description": "deep-link:deny-register -> Denies the register command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:deny-register"
]
},
{
"description": "deep-link:deny-unregister -> Denies the unregister command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:deny-unregister"
]
},
{
"type": "string",
"enum": [

View File

@@ -2512,6 +2512,69 @@
"clipboard-manager:deny-write-text"
]
},
{
"description": "deep-link:default -> Allows reading the opened deep link via the get_current command",
"type": "string",
"enum": [
"deep-link:default"
]
},
{
"description": "deep-link:allow-get-current -> Enables the get_current command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:allow-get-current"
]
},
{
"description": "deep-link:allow-is-registered -> Enables the is_registered command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:allow-is-registered"
]
},
{
"description": "deep-link:allow-register -> Enables the register command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:allow-register"
]
},
{
"description": "deep-link:allow-unregister -> Enables the unregister command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:allow-unregister"
]
},
{
"description": "deep-link:deny-get-current -> Denies the get_current command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:deny-get-current"
]
},
{
"description": "deep-link:deny-is-registered -> Denies the is_registered command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:deny-is-registered"
]
},
{
"description": "deep-link:deny-register -> Denies the register command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:deny-register"
]
},
{
"description": "deep-link:deny-unregister -> Denies the unregister command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:deny-unregister"
]
},
{
"type": "string",
"enum": [

View File

@@ -18,5 +18,5 @@ anyhow = "1.0.79"
hyper = { version = "0.14" }
hyper-rustls = { version = "0.24.0", features = ["http2"] }
uuid = { version = "1.7.0", features = ["v4"] }
tauri = { version = "2.0.0-beta" }
tauri-plugin-shell = "2.0.0-beta"
tauri = { version = "2.0.0-beta.16" }
tauri-plugin-shell = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }

View File

@@ -185,22 +185,20 @@ impl GrpcHandle {
pub async fn services_from_files(
&mut self,
id: &str,
uri: &str,
uri: &Uri,
paths: Vec<PathBuf>,
) -> Result<Vec<ServiceDefinition>, String> {
let pool = fill_pool_from_files(&self.app_handle, paths).await?;
let uri = Uri::from_str(uri).map_err(|e| e.to_string())?;
self.pools.insert(self.get_pool_key(id, &uri), pool.clone());
self.pools.insert(self.get_pool_key(id, uri), pool.clone());
Ok(self.services_from_pool(&pool))
}
pub async fn services_from_reflection(
&mut self,
id: &str,
uri: &str,
uri: &Uri,
) -> Result<Vec<ServiceDefinition>, String> {
let uri = Uri::from_str(uri).map_err(|e| e.to_string())?;
let pool = fill_pool(&uri).await?;
self.pools.insert(self.get_pool_key(id, &uri), pool.clone());
let pool = fill_pool(uri).await?;
self.pools.insert(self.get_pool_key(id, uri), pool.clone());
Ok(self.services_from_pool(&pool))
}
@@ -236,10 +234,9 @@ impl GrpcHandle {
pub async fn connect(
&mut self,
id: &str,
uri: &str,
uri: Uri,
proto_files: Vec<PathBuf>,
) -> Result<GrpcConnection, String> {
let uri = Uri::from_str(uri).map_err(|e| e.to_string())?;
let pool = match self.pools.get(id) {
Some(p) => p.clone(),
None => match proto_files.len() {

View File

@@ -1,14 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- Enable for v8 execution -->
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<!-- Re-enable for sandboxing. Currently disabled because auto-updater doesn't work with sandboxing.-->
<!-- <key>com.apple.security.app-sandbox</key> <true/>-->
<!-- <key>com.apple.security.files.user-selected.read-write</key> <true/>-->
<!-- <key>com.apple.security.network.client</key> <true/>-->
</dict>
<dict>
<!-- Re-enable for sandboxing. Currently disabled because auto-updater doesn't work with sandboxing.-->
<!-- <key>com.apple.security.app-sandbox</key> <true/>-->
<!-- <key>com.apple.security.files.user-selected.read-write</key> <true/>-->
<!-- <key>com.apple.security.network.client</key> <true/>-->
</dict>
</plist>

View File

@@ -1 +0,0 @@
ALTER TABLE settings ADD COLUMN open_workspace_new_window BOOLEAN NULL DEFAULT NULL;

View File

@@ -1,37 +1,36 @@
const o = `\\
`;
function y(p, t) {
var f, r, u, l, s, c;
const n = ["curl"];
t.method && n.push("-X", t.method), t.url && n.push(i(t.url)), n.push(o);
for (const a of (t.urlParameters ?? []).filter(h))
n.push("--url-query", i(`${a.name}=${a.value}`)), n.push(o);
for (const a of (t.headers ?? []).filter(h))
n.push("--header", i(`${a.name}: ${a.value}`)), n.push(o);
if (Array.isArray((f = t.body) == null ? void 0 : f.form)) {
const a = t.bodyType === "multipart/form-data" ? "--form" : "--data";
for (const e of (((r = t.body) == null ? void 0 : r.form) ?? []).filter(h)) {
function d(n) {
var h, f, r, u, l, s;
const t = ["curl"];
n.method && t.push("-X", n.method), n.url && t.push(i(n.url)), t.push(o);
for (const a of (n.urlParameters ?? []).filter(p))
t.push("--url-query", i(`${a.name}=${a.value}`)), t.push(o);
for (const a of (n.headers ?? []).filter(p))
t.push("--header", i(`${a.name}: ${a.value}`)), t.push(o);
if (Array.isArray((h = n.body) == null ? void 0 : h.form)) {
const a = n.bodyType === "multipart/form-data" ? "--form" : "--data";
for (const e of (((f = n.body) == null ? void 0 : f.form) ?? []).filter(p)) {
if (e.file) {
let d = `${e.name}=@${e.file}`;
d += e.contentType ? `;type=${e.contentType}` : "", n.push(a, d);
let c = `${e.name}=@${e.file}`;
c += e.contentType ? `;type=${e.contentType}` : "", t.push(a, c);
} else
n.push(a, i(`${e.name}=${e.value}`));
n.push(o);
t.push(a, i(`${e.name}=${e.value}`));
t.push(o);
}
} else
typeof ((u = t.body) == null ? void 0 : u.text) == "string" && (n.push("--data-raw", `$${i(t.body.text)}`), n.push(o));
return (t.authenticationType === "basic" || t.authenticationType === "digest") && (t.authenticationType === "digest" && n.push("--digest"), n.push(
typeof ((r = n.body) == null ? void 0 : r.text) == "string" && (t.push("--data-raw", `$${i(n.body.text)}`), t.push(o));
return (n.authenticationType === "basic" || n.authenticationType === "digest") && (n.authenticationType === "digest" && t.push("--digest"), t.push(
"--user",
i(`${((l = t.authentication) == null ? void 0 : l.username) ?? ""}:${((s = t.authentication) == null ? void 0 : s.password) ?? ""}`)
), n.push(o)), t.authenticationType === "bearer" && (n.push("--header", i(`Authorization: Bearer ${((c = t.authentication) == null ? void 0 : c.token) ?? ""}`)), n.push(o)), n[n.length - 1] === o && n.splice(n.length - 1, 1), n.join(" ");
i(`${((u = n.authentication) == null ? void 0 : u.username) ?? ""}:${((l = n.authentication) == null ? void 0 : l.password) ?? ""}`)
), t.push(o)), n.authenticationType === "bearer" && (t.push("--header", i(`Authorization: Bearer ${((s = n.authentication) == null ? void 0 : s.token) ?? ""}`)), t.push(o)), t[t.length - 1] === o && t.splice(t.length - 1, 1), t.join(" ");
}
function i(p) {
return `'${p.replace(/'/g, "\\'")}'`;
function i(n) {
return `'${n.replace(/'/g, "\\'")}'`;
}
function h(p) {
return p.enabled !== !1 && !!p.name;
function p(n) {
return n.enabled !== !1 && !!n.name;
}
export {
y as pluginHookExport
d as pluginHookExport
};
//# sourceMappingURL=index.mjs.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"index.mjs","sources":["../../../plugins/exporter-curl/src/index.ts"],"sourcesContent":["import { HttpRequest } from '../../../src-web/lib/models';\n\nconst NEWLINE = '\\\\\\n ';\n\nexport function pluginHookExport(_: any, request: Partial<HttpRequest>) {\n const xs = ['curl'];\n\n // Add method and URL all on first line\n if (request.method) xs.push('-X', request.method);\n if (request.url) xs.push(quote(request.url));\n\n xs.push(NEWLINE);\n\n // Add URL params\n for (const p of (request.urlParameters ?? []).filter(onlyEnabled)) {\n xs.push('--url-query', quote(`${p.name}=${p.value}`));\n xs.push(NEWLINE);\n }\n\n // Add headers\n for (const h of (request.headers ?? []).filter(onlyEnabled)) {\n xs.push('--header', quote(`${h.name}: ${h.value}`));\n xs.push(NEWLINE);\n }\n\n // Add form params\n if (Array.isArray(request.body?.form)) {\n const flag = request.bodyType === 'multipart/form-data' ? '--form' : '--data';\n for (const p of (request.body?.form ?? []).filter(onlyEnabled)) {\n if (p.file) {\n let v = `${p.name}=@${p.file}`;\n v += p.contentType ? `;type=${p.contentType}` : '';\n xs.push(flag, v);\n } else {\n xs.push(flag, quote(`${p.name}=${p.value}`));\n }\n xs.push(NEWLINE);\n }\n } else if (typeof request.body?.text === 'string') {\n // --data-raw $'...' to do special ANSI C quoting\n xs.push('--data-raw', `$${quote(request.body.text)}`);\n xs.push(NEWLINE);\n }\n\n // Add basic/digest authentication\n if (request.authenticationType === 'basic' || request.authenticationType === 'digest') {\n if (request.authenticationType === 'digest') xs.push('--digest');\n xs.push(\n '--user',\n quote(`${request.authentication?.username ?? ''}:${request.authentication?.password ?? ''}`),\n );\n xs.push(NEWLINE);\n }\n\n // Add bearer authentication\n if (request.authenticationType === 'bearer') {\n xs.push('--header', quote(`Authorization: Bearer ${request.authentication?.token ?? ''}`));\n xs.push(NEWLINE);\n }\n\n // Remove trailing newline\n if (xs[xs.length - 1] === NEWLINE) {\n xs.splice(xs.length - 1, 1);\n }\n\n return xs.join(' ');\n}\n\nfunction quote(arg: string): string {\n const escaped = arg.replace(/'/g, \"\\\\'\");\n return `'${escaped}'`;\n}\n\nfunction onlyEnabled(v: { name?: string; enabled?: boolean }): boolean {\n return v.enabled !== false && !!v.name;\n}\n"],"names":["NEWLINE","pluginHookExport","_","request","_a","_b","_c","_d","_e","_f","xs","quote","p","onlyEnabled","h","flag","v","arg"],"mappings":"AAEA,MAAMA,IAAU;AAAA;AAEA,SAAAC,EAAiBC,GAAQC,GAA+B;AAFxE,MAAAC,GAAAC,GAAAC,GAAAC,GAAAC,GAAAC;AAGQ,QAAAC,IAAK,CAAC,MAAM;AAGlB,EAAIP,EAAQ,UAAWO,EAAA,KAAK,MAAMP,EAAQ,MAAM,GAC5CA,EAAQ,OAAKO,EAAG,KAAKC,EAAMR,EAAQ,GAAG,CAAC,GAE3CO,EAAG,KAAKV,CAAO;AAGf,aAAWY,MAAMT,EAAQ,iBAAiB,IAAI,OAAOU,CAAW;AAC3D,IAAAH,EAAA,KAAK,eAAeC,EAAM,GAAGC,EAAE,IAAI,IAAIA,EAAE,KAAK,EAAE,CAAC,GACpDF,EAAG,KAAKV,CAAO;AAIjB,aAAWc,MAAMX,EAAQ,WAAW,IAAI,OAAOU,CAAW;AACrD,IAAAH,EAAA,KAAK,YAAYC,EAAM,GAAGG,EAAE,IAAI,KAAKA,EAAE,KAAK,EAAE,CAAC,GAClDJ,EAAG,KAAKV,CAAO;AAIjB,MAAI,MAAM,SAAQI,IAAAD,EAAQ,SAAR,gBAAAC,EAAc,IAAI,GAAG;AACrC,UAAMW,IAAOZ,EAAQ,aAAa,wBAAwB,WAAW;AAC1D,eAAAS,QAAMP,IAAAF,EAAQ,SAAR,gBAAAE,EAAc,SAAQ,CAAI,GAAA,OAAOQ,CAAW,GAAG;AAC9D,UAAID,EAAE,MAAM;AACV,YAAII,IAAI,GAAGJ,EAAE,IAAI,KAAKA,EAAE,IAAI;AAC5B,QAAAI,KAAKJ,EAAE,cAAc,SAASA,EAAE,WAAW,KAAK,IAC7CF,EAAA,KAAKK,GAAMC,CAAC;AAAA,MAAA;AAEZ,QAAAN,EAAA,KAAKK,GAAMJ,EAAM,GAAGC,EAAE,IAAI,IAAIA,EAAE,KAAK,EAAE,CAAC;AAE7C,MAAAF,EAAG,KAAKV,CAAO;AAAA,IACjB;AAAA,EACS;AAAA,IAAA,SAAOM,IAAAH,EAAQ,SAAR,gBAAAG,EAAc,SAAS,aAEpCI,EAAA,KAAK,cAAc,IAAIC,EAAMR,EAAQ,KAAK,IAAI,CAAC,EAAE,GACpDO,EAAG,KAAKV,CAAO;AAIjB,UAAIG,EAAQ,uBAAuB,WAAWA,EAAQ,uBAAuB,cACvEA,EAAQ,uBAAuB,YAAUO,EAAG,KAAK,UAAU,GAC5DA,EAAA;AAAA,IACD;AAAA,IACAC,EAAM,KAAGJ,IAAAJ,EAAQ,mBAAR,gBAAAI,EAAwB,aAAY,EAAE,MAAIC,IAAAL,EAAQ,mBAAR,gBAAAK,EAAwB,aAAY,EAAE,EAAE;AAAA,EAAA,GAE7FE,EAAG,KAAKV,CAAO,IAIbG,EAAQ,uBAAuB,aAC9BO,EAAA,KAAK,YAAYC,EAAM,2BAAyBF,IAAAN,EAAQ,mBAAR,gBAAAM,EAAwB,UAAS,EAAE,EAAE,CAAC,GACzFC,EAAG,KAAKV,CAAO,IAIbU,EAAGA,EAAG,SAAS,CAAC,MAAMV,KACxBU,EAAG,OAAOA,EAAG,SAAS,GAAG,CAAC,GAGrBA,EAAG,KAAK,GAAG;AACpB;AAEA,SAASC,EAAMM,GAAqB;AAElC,SAAO,IADSA,EAAI,QAAQ,MAAM,KAAK,CACrB;AACpB;AAEA,SAASJ,EAAYG,GAAkD;AACrE,SAAOA,EAAE,YAAY,MAAS,CAAC,CAACA,EAAE;AACpC;"}

View File

@@ -1,15 +1,15 @@
var Xe = typeof globalThis < "u" ? globalThis : typeof window < "u" ? window : typeof global < "u" ? global : typeof self < "u" ? self : {};
function Nt(ce) {
return ce && ce.__esModule && Object.prototype.hasOwnProperty.call(ce, "default") ? ce.default : ce;
function Nt(le) {
return le && le.__esModule && Object.prototype.hasOwnProperty.call(le, "default") ? le.default : le;
}
function Be(ce) {
throw new Error('Could not dynamically require "' + ce + '". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.');
function Be(le) {
throw new Error('Could not dynamically require "' + le + '". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.');
}
var Ve = { exports: {} };
/*! jsonpath 1.1.1 */
(function(ce, Ne) {
(function(le, Ne) {
(function(T) {
ce.exports = T();
le.exports = T();
})(function() {
return function T(L, B, v) {
function y(E, d) {
@@ -1131,7 +1131,7 @@ var Ve = { exports: {} };
function Ze() {
var e = [], t;
for (t = i, O("["); !_("]"); )
_(",") ? (N(), e.push(null)) : (e.push(le()), _("]") || O(","));
_(",") ? (N(), e.push(null)) : (e.push(ce()), _("]") || O(","));
return N(), n.markEnd(n.createArrayExpression(e), t);
}
function _e(e, t) {
@@ -1145,11 +1145,11 @@ var Ve = { exports: {} };
function et() {
var e, t, u, l, C, x;
if (e = i, x = i, e.type === y.Identifier)
return u = De(), e.value === "get" && !_(":") ? (t = De(), O("("), O(")"), l = _e([]), n.markEnd(n.createProperty("get", t, l), x)) : e.value === "set" && !_(":") ? (t = De(), O("("), e = i, e.type !== y.Identifier ? (O(")"), G(e, a.UnexpectedToken, e.value), l = _e([])) : (C = [ye()], O(")"), l = _e(C, e)), n.markEnd(n.createProperty("set", t, l), x)) : (O(":"), l = le(), n.markEnd(n.createProperty("init", u, l), x));
return u = De(), e.value === "get" && !_(":") ? (t = De(), O("("), O(")"), l = _e([]), n.markEnd(n.createProperty("get", t, l), x)) : e.value === "set" && !_(":") ? (t = De(), O("("), e = i, e.type !== y.Identifier ? (O(")"), G(e, a.UnexpectedToken, e.value), l = _e([])) : (C = [ye()], O(")"), l = _e(C, e)), n.markEnd(n.createProperty("set", t, l), x)) : (O(":"), l = ce(), n.markEnd(n.createProperty("init", u, l), x));
if (e.type === y.EOF || e.type === y.Punctuator)
pe(e);
else
return t = De(), O(":"), l = le(), n.markEnd(n.createProperty("init", t, l), x);
return t = De(), O(":"), l = ce(), n.markEnd(n.createProperty("init", t, l), x);
}
function tt() {
var e = [], t, u, l, C, x = {}, j = String, W;
@@ -1184,7 +1184,7 @@ var Ve = { exports: {} };
function je() {
var e = [];
if (O("("), !_(")"))
for (; r < h && (e.push(le()), !_(")")); )
for (; r < h && (e.push(ce()), !_(")")); )
O(",");
return O(")"), e;
}
@@ -1300,17 +1300,17 @@ var Ve = { exports: {} };
}
function st() {
var e, t, u, l, C;
return C = i, e = at(), _("?") && (N(), t = p.allowIn, p.allowIn = !0, u = le(), p.allowIn = t, O(":"), l = le(), e = n.createConditionalExpression(e, u, l), n.markEnd(e, C)), e;
return C = i, e = at(), _("?") && (N(), t = p.allowIn, p.allowIn = !0, u = ce(), p.allowIn = t, O(":"), l = ce(), e = n.createConditionalExpression(e, u, l), n.markEnd(e, C)), e;
}
function le() {
function ce() {
var e, t, u, l, C;
return e = i, C = i, l = t = st(), Ye() && (be(t) || G({}, a.InvalidLHSInAssignment), c && t.type === E.Identifier && J(t.name) && G(e, a.StrictLHSAssignment), e = N(), u = le(), l = n.markEnd(n.createAssignmentExpression(e.value, t, u), C)), l;
return e = i, C = i, l = t = st(), Ye() && (be(t) || G({}, a.InvalidLHSInAssignment), c && t.type === E.Identifier && J(t.name) && G(e, a.StrictLHSAssignment), e = N(), u = ce(), l = n.markEnd(n.createAssignmentExpression(e.value, t, u), C)), l;
}
function te() {
var e, t = i;
if (e = le(), _(",")) {
if (e = ce(), _(",")) {
for (e = n.createSequenceExpression([e]); r < h && _(","); )
N(), e.expressions.push(le());
N(), e.expressions.push(ce());
n.markEnd(e, t);
}
return e;
@@ -1330,7 +1330,7 @@ var Ve = { exports: {} };
}
function lt(e) {
var t = null, u, l;
return l = i, u = ye(), c && J(u.name) && G({}, a.StrictVarName), e === "const" ? (O("="), t = le()) : _("=") && (N(), t = le()), n.markEnd(n.createVariableDeclarator(u, t), l);
return l = i, u = ye(), c && J(u.name) && G({}, a.StrictVarName), e === "const" ? (O("="), t = ce()) : _("=") && (N(), t = ce()), n.markEnd(n.createVariableDeclarator(u, t), l);
}
function Te(e) {
var t = [];
@@ -3176,17 +3176,16 @@ Expecting ` + se.join(", ") + ", got '" + (this.terminals_[w] || w) + "'" : ie =
})(Ve);
var Pt = Ve.exports;
const Lt = /* @__PURE__ */ Nt(Pt);
function Rt(ce, Ne, T) {
let L;
function Rt(le, Ne) {
let T;
try {
L = JSON.parse(T);
T = JSON.parse(Ne);
} catch {
return;
}
const B = Lt.query(L, Ne);
return { filtered: JSON.stringify(B, null, 2) };
const L = Lt.query(T, le);
return { filtered: JSON.stringify(L, null, 2) };
}
export {
Rt as pluginHookResponseFilter
};
//# sourceMappingURL=index.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -6116,11 +6116,10 @@ Tr.__DOMHandler = Dr;
Tr.normalizeLineEndings = Rt;
Tr.DOMParser = _t;
var Cu = Tr.DOMParser;
function yu(t, u, n) {
const s = new Cu().parseFromString(n, "text/xml");
return { filtered: `${Qt.select(u, s)}` };
function yu(t, u) {
const n = new Cu().parseFromString(u, "text/xml");
return { filtered: `${Qt.select(t, n)}` };
}
export {
yu as pluginHookResponseFilter
};
//# sourceMappingURL=index.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -9,86 +9,86 @@ var j = "(?:" + [
">\\&",
"<\\&",
"[&;()|<>]"
].join("|") + ")", D = new RegExp("^" + j + "$"), _ = "|&;()<> \\t", M = '"((\\\\"|[^"])*?)"', Q = "'((\\\\'|[^'])*?)'", V = /^#$/, q = "'", G = '"', U = "$", R = "", z = 4294967296;
].join("|") + ")", D = new RegExp("^" + j + "$"), q = "|&;()<> \\t", M = '"((\\\\"|[^"])*?)"', Q = "'((\\\\'|[^'])*?)'", V = /^#$/, _ = "'", G = '"', U = "$", $ = "", z = 4294967296;
for (var L = 0; L < 4; L++)
R += (z * Math.random()).toString(16);
var J = new RegExp("^" + R);
$ += (z * Math.random()).toString(16);
var J = new RegExp("^" + $);
function X(n, s) {
for (var t = s.lastIndex, r = [], l; l = s.exec(n); )
r.push(l), s.lastIndex === l.index && (s.lastIndex += 1);
return s.lastIndex = t, r;
for (var e = s.lastIndex, t = [], c; c = s.exec(n); )
t.push(c), s.lastIndex === c.index && (s.lastIndex += 1);
return s.lastIndex = e, t;
}
function F(n, s, t) {
var r = typeof n == "function" ? n(t) : n[t];
return typeof r > "u" && t != "" ? r = "" : typeof r > "u" && (r = "$"), typeof r == "object" ? s + R + JSON.stringify(r) + R : s + r;
function F(n, s, e) {
var t = typeof n == "function" ? n(e) : n[e];
return typeof t > "u" && e != "" ? t = "" : typeof t > "u" && (t = "$"), typeof t == "object" ? s + $ + JSON.stringify(t) + $ : s + t;
}
function K(n, s, t) {
t || (t = {});
var r = t.escape || "\\", l = "(\\" + r + `['"` + _ + `]|[^\\s'"` + _ + "])+", h = new RegExp([
function K(n, s, e) {
e || (e = {});
var t = e.escape || "\\", c = "(\\" + t + `['"` + q + `]|[^\\s'"` + q + "])+", m = new RegExp([
"(" + j + ")",
// control chars
"(" + l + "|" + M + "|" + Q + ")+"
].join("|"), "g"), p = X(n, h);
if (p.length === 0)
"(" + c + "|" + M + "|" + Q + ")+"
].join("|"), "g"), f = X(n, m);
if (f.length === 0)
return [];
s || (s = {});
var g = !1;
return p.map(function(d) {
var e = d[0];
if (!e || g)
var w = !1;
return f.map(function(r) {
var a = r[0];
if (!a || w)
return;
if (D.test(e))
return { op: e };
var c = !1, T = !1, m = "", O = !1, a;
function $() {
a += 1;
var x, f, C = e.charAt(a);
if (C === "{") {
if (a += 1, e.charAt(a) === "}")
throw new Error("Bad substitution: " + e.slice(a - 2, a + 1));
if (x = e.indexOf("}", a), x < 0)
throw new Error("Bad substitution: " + e.slice(a));
f = e.slice(a, x), a = x;
} else if (/[*@#?$!_-]/.test(C))
f = C, a += 1;
if (D.test(a))
return { op: a };
var x = !1, C = !1, d = "", O = !1, i;
function T() {
i += 1;
var v, p, R = a.charAt(i);
if (R === "{") {
if (i += 1, a.charAt(i) === "}")
throw new Error("Bad substitution: " + a.slice(i - 2, i + 1));
if (v = a.indexOf("}", i), v < 0)
throw new Error("Bad substitution: " + a.slice(i));
p = a.slice(i, v), i = v;
} else if (/[*@#?$!_-]/.test(R))
p = R, i += 1;
else {
var w = e.slice(a);
x = w.match(/[^\w\d_]/), x ? (f = w.slice(0, x.index), a += x.index - 1) : (f = w, a = e.length);
var g = a.slice(i);
v = g.match(/[^\w\d_]/), v ? (p = g.slice(0, v.index), i += v.index - 1) : (p = g, i = a.length);
}
return F(s, "", f);
return F(s, "", p);
}
for (a = 0; a < e.length; a++) {
var u = e.charAt(a);
if (O = O || !c && (u === "*" || u === "?"), T)
m += u, T = !1;
else if (c)
u === c ? c = !1 : c == q ? m += u : u === r ? (a += 1, u = e.charAt(a), u === G || u === r || u === U ? m += u : m += r + u) : u === U ? m += $() : m += u;
else if (u === G || u === q)
c = u;
for (i = 0; i < a.length; i++) {
var u = a.charAt(i);
if (O = O || !x && (u === "*" || u === "?"), C)
d += u, C = !1;
else if (x)
u === x ? x = !1 : x == _ ? d += u : u === t ? (i += 1, u = a.charAt(i), u === G || u === t || u === U ? d += u : d += t + u) : u === U ? d += T() : d += u;
else if (u === G || u === _)
x = u;
else {
if (D.test(u))
return { op: e };
return { op: a };
if (V.test(u)) {
g = !0;
var b = { comment: n.slice(d.index + a + 1) };
return m.length ? [m, b] : [b];
w = !0;
var b = { comment: n.slice(r.index + i + 1) };
return d.length ? [d, b] : [b];
} else
u === r ? T = !0 : u === U ? m += $() : m += u;
u === t ? C = !0 : u === U ? d += T() : d += u;
}
}
return O ? { op: "glob", pattern: m } : m;
}).reduce(function(d, e) {
return typeof e > "u" ? d : d.concat(e);
return O ? { op: "glob", pattern: d } : d;
}).reduce(function(r, a) {
return typeof a > "u" ? r : r.concat(a);
}, []);
}
var Y = function(s, t, r) {
var l = K(s, t, r);
return typeof t != "function" ? l : l.reduce(function(h, p) {
if (typeof p == "object")
return h.concat(p);
var g = p.split(RegExp("(" + R + ".*?" + R + ")", "g"));
return g.length === 1 ? h.concat(g[0]) : h.concat(g.filter(Boolean).map(function(d) {
return J.test(d) ? JSON.parse(d.split(R)[1]) : d;
var Y = function(s, e, t) {
var c = K(s, e, t);
return typeof e != "function" ? c : c.reduce(function(m, f) {
if (typeof f == "object")
return m.concat(f);
var w = f.split(RegExp("(" + $ + ".*?" + $ + ")", "g"));
return w.length === 1 ? m.concat(w[0]) : m.concat(w.filter(Boolean).map(function(r) {
return J.test(r) ? JSON.parse(r.split($)[1]) : r;
}));
}, []);
}, Z = Y;
@@ -115,138 +115,138 @@ const ae = "curl", se = "cURL", ie = "cURL command line tool", H = ["d", "data",
// Request method
H
].flatMap((n) => n);
function oe(n, s) {
if (!s.match(/^\s*curl /))
function oe(n) {
if (!n.match(/^\s*curl /))
return null;
const t = [], r = s.replace(/\ncurl/g, "; curl");
let l = [];
const p = Z(r).flatMap((e) => typeof e == "string" && e.startsWith("-") && !e.startsWith("--") && e.length > 2 ? [e.slice(0, 2), e.slice(2)] : e);
for (const e of p) {
if (typeof e == "string") {
e.startsWith("$") ? l.push(e.slice(1)) : l.push(e);
const s = [], e = n.replace(/\ncurl/g, "; curl");
let t = [];
const m = Z(e).flatMap((r) => typeof r == "string" && r.startsWith("-") && !r.startsWith("--") && r.length > 2 ? [r.slice(0, 2), r.slice(2)] : r);
for (const r of m) {
if (typeof r == "string") {
r.startsWith("$") ? t.push(r.slice(1)) : t.push(r);
continue;
}
if ("comment" in e)
if ("comment" in r)
continue;
const { op: c } = e;
if (c === ";") {
t.push(l), l = [];
const { op: a } = r;
if (a === ";") {
s.push(t), t = [];
continue;
}
if (c != null && c.startsWith("$")) {
const T = c.slice(2, c.length - 1).replace(/\\'/g, "'");
l.push(T);
if (a != null && a.startsWith("$")) {
const x = a.slice(2, a.length - 1).replace(/\\'/g, "'");
t.push(x);
continue;
}
c === "glob" && l.push(e.pattern);
a === "glob" && t.push(r.pattern);
}
t.push(l);
const g = {
s.push(t);
const f = {
model: "workspace",
id: N("workspace"),
name: "Curl Import"
};
return {
resources: {
httpRequests: t.filter((e) => e[0] === "curl").map((e) => te(e, g.id)),
workspaces: [g]
httpRequests: s.filter((r) => r[0] === "curl").map((r) => te(r, f.id)),
workspaces: [f]
}
};
}
function te(n, s) {
const t = {}, r = [];
for (let i = 1; i < n.length; i++) {
let o = n[i];
if (typeof o == "string" && (o = o.trim()), typeof o == "string" && o.match(/^-{1,2}[\w-]+/)) {
const E = o[0] === "-" && o[1] !== "-";
let v = o.replace(/^-{1,2}/, "");
if (!ee.includes(v))
const e = {}, t = [];
for (let o = 1; o < n.length; o++) {
let l = n[o];
if (typeof l == "string" && (l = l.trim()), typeof l == "string" && l.match(/^-{1,2}[\w-]+/)) {
const E = l[0] === "-" && l[1] !== "-";
let h = l.replace(/^-{1,2}/, "");
if (!ee.includes(h))
continue;
let y;
const S = n[i + 1];
E && v.length > 1 ? (y = v.slice(1), v = v.slice(0, 1)) : typeof S == "string" && !S.startsWith("-") ? (y = S, i++) : y = !0, t[v] = t[v] || [], t[v].push(y);
const S = n[o + 1];
E && h.length > 1 ? (y = h.slice(1), h = h.slice(0, 1)) : typeof S == "string" && !S.startsWith("-") ? (y = S, o++) : y = !0, e[h] = e[h] || [], e[h].push(y);
} else
o && r.push(o);
l && t.push(l);
}
let l, h;
const p = A(t, r[0] || "", ["url"]), [g, d] = W(p, "?");
l = (d == null ? void 0 : d.split("&").map((i) => {
const o = W(i, "=");
return { name: o[0] ?? "", value: o[1] ?? "", enabled: !0 };
})) ?? [], h = g ?? p;
const [e, c] = A(t, "", ["u", "user"]).split(/:(.*)$/), T = A(t, !1, ["digest"]), m = e ? T ? "digest" : "basic" : null, O = e ? {
username: e.trim(),
password: (c ?? "").trim()
} : {}, a = [
...t.header || [],
...t.H || []
].map((i) => {
const [o, E] = i.split(/:(.*)$/);
let c, m;
const f = A(e, t[0] || "", ["url"]), [w, r] = W(f, "?");
c = (r == null ? void 0 : r.split("&").map((o) => {
const l = W(o, "=");
return { name: l[0] ?? "", value: l[1] ?? "", enabled: !0 };
})) ?? [], m = w ?? f;
const [a, x] = A(e, "", ["u", "user"]).split(/:(.*)$/), C = A(e, !1, ["digest"]), d = a ? C ? "digest" : "basic" : null, O = a ? {
username: a.trim(),
password: (x ?? "").trim()
} : {}, i = [
...e.header || [],
...e.H || []
].map((o) => {
const [l, E] = o.split(/:(.*)$/);
return E ? {
name: (o ?? "").trim(),
name: (l ?? "").trim(),
value: E.trim(),
enabled: !0
} : {
name: (o ?? "").trim().replace(/;$/, ""),
name: (l ?? "").trim().replace(/;$/, ""),
value: "",
enabled: !0
};
}), $ = [
...t.cookie || [],
...t.b || []
].map((i) => {
const o = i.split("=", 1)[0], E = i.replace(`${o}=`, "");
return `${o}=${E}`;
}).join("; "), u = a.find((i) => i.name.toLowerCase() === "cookie");
$ && u ? u.value += `; ${$}` : $ && a.push({
}), T = [
...e.cookie || [],
...e.b || []
].map((o) => {
const l = o.split("=", 1)[0], E = o.replace(`${l}=`, "");
return `${l}=${E}`;
}).join("; "), u = i.find((o) => o.name.toLowerCase() === "cookie");
T && u ? u.value += `; ${T}` : T && i.push({
name: "Cookie",
value: $,
value: T,
enabled: !0
});
const b = ne(t), x = a.find((i) => i.name.toLowerCase() === "content-type"), f = x ? x.value.split(";")[0] : null, C = [
...t.form || [],
...t.F || []
].map((i) => {
const o = i.split("="), E = o[0] ?? "", v = o[1] ?? "", y = {
const b = ne(e), v = i.find((o) => o.name.toLowerCase() === "content-type"), p = v ? v.value.split(";")[0] : null, R = [
...e.form || [],
...e.F || []
].map((o) => {
const l = o.split("="), E = l[0] ?? "", h = l[1] ?? "", y = {
name: E,
enabled: !0
};
return v.indexOf("@") === 0 ? y.file = v.slice(1) : y.value = v, y;
return h.indexOf("@") === 0 ? y.file = h.slice(1) : y.value = h, y;
});
let w = {}, I = null;
const B = A(t, !1, ["G", "get"]);
b.length > 0 && B ? l.push(...b) : b.length > 0 && (f == null || f === "application/x-www-form-urlencoded") ? (I = f ?? "application/x-www-form-urlencoded", w = {
form: b.map((i) => ({
...i,
name: decodeURIComponent(i.name || ""),
value: decodeURIComponent(i.value || "")
let g = {}, I = null;
const B = A(e, !1, ["G", "get"]);
b.length > 0 && B ? c.push(...b) : b.length > 0 && (p == null || p === "application/x-www-form-urlencoded") ? (I = p ?? "application/x-www-form-urlencoded", g = {
form: b.map((o) => ({
...o,
name: decodeURIComponent(o.name || ""),
value: decodeURIComponent(o.value || "")
}))
}, a.push({
}, i.push({
name: "Content-Type",
value: "application/x-www-form-urlencoded",
enabled: !0
})) : b.length > 0 ? (I = f === "application/json" || f === "text/xml" || f === "text/plain" ? f : "other", w = {
text: b.map(({ name: i, value: o }) => i && o ? `${i}=${o}` : i || o).join("&")
}) : C.length && (I = f ?? "multipart/form-data", w = {
form: C
}, f == null && a.push({
})) : b.length > 0 ? (I = p === "application/json" || p === "text/xml" || p === "text/plain" ? p : "other", g = {
text: b.map(({ name: o, value: l }) => o && l ? `${o}=${l}` : o || l).join("&")
}) : R.length && (I = p ?? "multipart/form-data", g = {
form: R
}, p == null && i.push({
name: "Content-Type",
value: "multipart/form-data",
enabled: !0
}));
let P = A(t, "", ["X", "request"]).toUpperCase();
return P === "" && w && (P = "text" in w || "form" in w ? "POST" : "GET"), {
let P = A(e, "", ["X", "request"]).toUpperCase();
return P === "" && g && (P = "text" in g || "form" in g ? "POST" : "GET"), {
id: N("http_request"),
model: "http_request",
workspaceId: s,
name: "",
urlParameters: l,
url: h,
urlParameters: c,
url: m,
method: P,
headers: a,
headers: i,
authentication: O,
authenticationType: m,
body: w,
authenticationType: d,
body: g,
bodyType: I,
folderId: null,
sortPriority: 0
@@ -254,35 +254,35 @@ function te(n, s) {
}
const ne = (n) => {
let s = [];
for (const t of H) {
const r = n[t];
if (!(!r || r.length === 0))
for (const l of r) {
if (typeof l != "string")
for (const e of H) {
const t = n[e];
if (!(!t || t.length === 0))
for (const c of t) {
if (typeof c != "string")
continue;
const [h, p] = l.split("=");
l.startsWith("@") ? s.push({
name: h ?? "",
const [m, f] = c.split("=");
c.startsWith("@") ? s.push({
name: m ?? "",
value: "",
filePath: l.slice(1),
filePath: c.slice(1),
enabled: !0
}) : s.push({
name: h ?? "",
value: t === "data-urlencode" ? encodeURIComponent(p ?? "") : p ?? "",
name: m ?? "",
value: e === "data-urlencode" ? encodeURIComponent(f ?? "") : f ?? "",
enabled: !0
});
}
}
return s;
}, A = (n, s, t) => {
for (const r of t)
if (n[r] && n[r].length)
return n[r][0];
}, A = (n, s, e) => {
for (const t of e)
if (n[t] && n[t].length)
return n[t][0];
return s;
};
function W(n, s) {
const t = n.indexOf(s);
return t > -1 ? [n.slice(0, t), n.slice(t + 1)] : [n];
const e = n.indexOf(s);
return e > -1 ? [n.slice(0, e), n.slice(e + 1)] : [n];
}
const k = {};
function N(n) {
@@ -295,4 +295,3 @@ export {
se as name,
oe as pluginHookImport
};
//# sourceMappingURL=index.mjs.map

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,73 +1,72 @@
const _ = "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", C = "https://schema.getpostman.com/json/collection/v2.0.0/collection.json", O = [C, _];
function v(e, t) {
var w;
console.log("CTX", e);
const a = q(t);
if (a == null)
const S = "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", _ = "https://schema.getpostman.com/json/collection/v2.0.0/collection.json", O = [_, S];
function v(e) {
var g;
const t = k(e);
if (t == null)
return;
const s = i(a.info);
if (!O.includes(s.schema) || !Array.isArray(a.item))
const o = i(t.info);
if (!O.includes(o.schema) || !Array.isArray(t.item))
return;
const u = k(a.auth), n = {
const u = A(t.auth), s = {
workspaces: [],
environments: [],
httpRequests: [],
folders: []
}, p = {
}, n = {
model: "workspace",
id: b("workspace"),
name: s.name || "Postman Import",
description: s.description || "",
variables: ((w = a.variable) == null ? void 0 : w.map((r) => ({
id: h("workspace"),
name: o.name || "Postman Import",
description: o.description || "",
variables: ((g = t.variable) == null ? void 0 : g.map((r) => ({
name: r.key,
value: r.value
}))) ?? []
};
n.workspaces.push(p);
const g = (r, d = null) => {
s.workspaces.push(n);
const T = (r, p = null) => {
if (typeof r.name == "string" && Array.isArray(r.item)) {
const o = {
const a = {
model: "folder",
workspaceId: p.id,
id: b("folder"),
workspaceId: n.id,
id: h("folder"),
name: r.name,
folderId: d
folderId: p
};
n.folders.push(o);
s.folders.push(a);
for (const l of r.item)
g(l, o.id);
T(l, a.id);
} else if (typeof r.name == "string" && "request" in r) {
const o = i(r.request), l = j(o.body), A = k(o.auth), m = A.authenticationType == null ? u : A, S = {
const a = i(r.request), l = j(a.body), w = A(a.auth), d = w.authenticationType == null ? u : w, q = {
model: "http_request",
id: b("http_request"),
workspaceId: p.id,
folderId: d,
id: h("http_request"),
workspaceId: n.id,
folderId: p,
name: r.name,
method: o.method || "GET",
url: typeof o.url == "string" ? o.url : i(o.url).raw,
method: a.method || "GET",
url: typeof a.url == "string" ? a.url : i(a.url).raw,
body: l.body,
bodyType: l.bodyType,
authentication: m.authentication,
authenticationType: m.authenticationType,
authentication: d.authentication,
authenticationType: d.authenticationType,
headers: [
...l.headers,
...m.headers,
...f(o.header).map((y) => ({
name: y.key,
value: y.value,
enabled: !y.disabled
...d.headers,
...b(a.header).map((m) => ({
name: m.key,
value: m.value,
enabled: !m.disabled
}))
]
};
n.httpRequests.push(S);
s.httpRequests.push(q);
} else
console.log("Unknown item", r, d);
console.log("Unknown item", r, p);
};
for (const r of a.item)
g(r);
return { resources: T(n) };
for (const r of t.item)
T(r);
return { resources: f(s) };
}
function k(e) {
function A(e) {
const t = i(e);
return "basic" in t ? {
headers: [],
@@ -85,7 +84,7 @@ function k(e) {
} : { headers: [], authenticationType: null, authentication: {} };
}
function j(e) {
var a, s, c, u;
var o, c, u, s;
const t = i(e);
return "graphql" in t ? {
headers: [
@@ -98,7 +97,7 @@ function j(e) {
bodyType: "graphql",
body: {
text: JSON.stringify(
{ query: t.graphql.query, variables: q(t.graphql.variables) },
{ query: t.graphql.query, variables: k(t.graphql.variables) },
null,
2
)
@@ -113,7 +112,7 @@ function j(e) {
],
bodyType: "application/x-www-form-urlencoded",
body: {
form: f(t.urlencoded).map((n) => ({
form: b(t.urlencoded).map((n) => ({
enabled: !n.disabled,
name: n.key ?? "",
value: n.value ?? ""
@@ -129,7 +128,7 @@ function j(e) {
],
bodyType: "multipart/form-data",
body: {
form: f(t.formdata).map(
form: b(t.formdata).map(
(n) => n.src != null ? {
enabled: !n.disabled,
contentType: n.contentType ?? null,
@@ -146,17 +145,17 @@ function j(e) {
headers: [
{
name: "Content-Type",
value: ((s = (a = t.options) == null ? void 0 : a.raw) == null ? void 0 : s.language) === "json" ? "application/json" : "",
value: ((c = (o = t.options) == null ? void 0 : o.raw) == null ? void 0 : c.language) === "json" ? "application/json" : "",
enabled: !0
}
],
bodyType: ((u = (c = t.options) == null ? void 0 : c.raw) == null ? void 0 : u.language) === "json" ? "application/json" : "other",
bodyType: ((s = (u = t.options) == null ? void 0 : u.raw) == null ? void 0 : s.language) === "json" ? "application/json" : "other",
body: {
text: t.raw ?? ""
}
} : { headers: [], bodyType: null, body: {} };
}
function q(e) {
function k(e) {
try {
return i(JSON.parse(e));
} catch {
@@ -166,19 +165,18 @@ function q(e) {
function i(e) {
return Object.prototype.toString.call(e) === "[object Object]" ? e : {};
}
function f(e) {
function b(e) {
return Object.prototype.toString.call(e) === "[object Array]" ? e : [];
}
function T(e) {
return typeof e == "string" ? e.replace(/{{\s*(_\.)?([^}]+)\s*}}/g, "${[$2]}") : Array.isArray(e) && e != null ? e.map(T) : typeof e == "object" && e != null ? Object.fromEntries(
Object.entries(e).map(([t, a]) => [t, T(a)])
function f(e) {
return typeof e == "string" ? e.replace(/{{\s*(_\.)?([^}]+)\s*}}/g, "${[$2]}") : Array.isArray(e) && e != null ? e.map(f) : typeof e == "object" && e != null ? Object.fromEntries(
Object.entries(e).map(([t, o]) => [t, f(o)])
) : e;
}
const h = {};
function b(e) {
return h[e] = (h[e] ?? -1) + 1, `GENERATE_ID::${e.toUpperCase()}_${h[e]}`;
const y = {};
function h(e) {
return y[e] = (y[e] ?? -1) + 1, `GENERATE_ID::${e.toUpperCase()}_${y[e]}`;
}
export {
v as pluginHookImport
};
//# sourceMappingURL=index.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -1,18 +1,17 @@
function u(r, t) {
function u(r) {
let e;
try {
e = JSON.parse(t);
e = JSON.parse(r);
} catch {
return;
}
if (!(!s(e) || !("yaakSchema" in e)))
if (!(!t(e) || !("yaakSchema" in e)))
return "requests" in e.resources && (e.resources.httpRequests = e.resources.requests, delete e.resources.requests), { resources: e.resources };
}
function s(r) {
function t(r) {
return Object.prototype.toString.call(r) === "[object Object]";
}
export {
s as isJSObject,
t as isJSObject,
u as pluginHookImport
};
//# sourceMappingURL=index.mjs.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"index.mjs","sources":["../../../plugins/importer-yaak/src/index.ts"],"sourcesContent":["export function pluginHookImport(ctx: any, contents: string) {\n let parsed;\n try {\n parsed = JSON.parse(contents);\n } catch (err) {\n return undefined;\n }\n\n if (!isJSObject(parsed)) {\n return undefined;\n }\n\n const isYaakExport = 'yaakSchema' in parsed;\n if (!isYaakExport) {\n return;\n }\n\n // Migrate v1 to v2 -- changes requests to httpRequests\n if ('requests' in parsed.resources) {\n parsed.resources.httpRequests = parsed.resources.requests;\n delete parsed.resources['requests'];\n }\n\n return { resources: parsed.resources }; // Should already be in the correct format\n}\n\nexport function isJSObject(obj: any) {\n return Object.prototype.toString.call(obj) === '[object Object]';\n}\n"],"names":["pluginHookImport","ctx","contents","parsed","isJSObject","obj"],"mappings":"AAAgB,SAAAA,EAAiBC,GAAUC,GAAkB;AACvD,MAAAC;AACA,MAAA;AACO,IAAAA,IAAA,KAAK,MAAMD,CAAQ;AAAA,UAChB;AACL;AAAA,EACT;AAOA,MALI,GAACE,EAAWD,CAAM,KAKlB,EADiB,gBAAgBA;AAMjC,WAAA,cAAcA,EAAO,cAChBA,EAAA,UAAU,eAAeA,EAAO,UAAU,UAC1C,OAAAA,EAAO,UAAU,WAGnB,EAAE,WAAWA,EAAO;AAC7B;AAEO,SAASC,EAAWC,GAAU;AACnC,SAAO,OAAO,UAAU,SAAS,KAAKA,CAAG,MAAM;AACjD;"}

View File

@@ -1,6 +1,6 @@
use std::fmt::Display;
use log::{debug, info};
use log::{debug, warn};
use serde::{Deserialize, Serialize};
use serde_json::json;
use sqlx::types::JsonValue;
@@ -194,7 +194,10 @@ pub async fn track_event(
}
if let Err(e) = req.send().await {
info!("Error sending analytics event: {}", e);
warn!(
"Error sending analytics event: {} {} {} {:?}",
e, event, attributes_json, params,
);
}
}

View File

@@ -1,237 +0,0 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
//! This example shows how to use swc to transpile TypeScript and JSX/TSX
//! modules.
//!
//! It will only transpile, not typecheck (like Deno's `--no-check` flag).
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::Arc;
use crate::deno_ops::op_yaml_parse;
use anyhow::anyhow;
use anyhow::bail;
use anyhow::Context;
use anyhow::Error;
use deno_ast::ParseParams;
use deno_ast::{EmitOptions, MediaType, SourceMapOption, TranspileOptions};
use deno_core::error::{AnyError, JsError};
use deno_core::resolve_path;
use deno_core::JsRuntime;
use deno_core::ModuleLoadResponse;
use deno_core::ModuleLoader;
use deno_core::ModuleSource;
use deno_core::ModuleSourceCode;
use deno_core::ModuleSpecifier;
use deno_core::ModuleType;
use deno_core::RequestedModuleType;
use deno_core::ResolutionKind;
use deno_core::RuntimeOptions;
use deno_core::SourceMapGetter;
use deno_core::{resolve_import, v8};
use tokio::task::block_in_place;
#[derive(Clone)]
struct SourceMapStore(Rc<RefCell<HashMap<String, Vec<u8>>>>);
impl SourceMapGetter for SourceMapStore {
fn get_source_map(&self, specifier: &str) -> Option<Vec<u8>> {
self.0.borrow().get(specifier).cloned()
}
fn get_source_line(&self, _file_name: &str, _line_number: usize) -> Option<String> {
None
}
}
struct TypescriptModuleLoader {
source_maps: SourceMapStore,
}
impl ModuleLoader for TypescriptModuleLoader {
fn resolve(
&self,
specifier: &str,
referrer: &str,
_kind: ResolutionKind,
) -> Result<ModuleSpecifier, Error> {
Ok(resolve_import(specifier, referrer)?)
}
fn load(
&self,
module_specifier: &ModuleSpecifier,
_maybe_referrer: Option<&ModuleSpecifier>,
_is_dyn_import: bool,
_requested_module_type: RequestedModuleType,
) -> ModuleLoadResponse {
let source_maps = self.source_maps.clone();
fn load(
source_maps: SourceMapStore,
module_specifier: &ModuleSpecifier,
) -> Result<ModuleSource, AnyError> {
let path = module_specifier
.to_file_path()
.map_err(|_| anyhow!("Only file:// URLs are supported."))?;
let media_type = MediaType::from_path(&path);
let (module_type, should_transpile) = match MediaType::from_path(&path) {
MediaType::JavaScript | MediaType::Mjs | MediaType::Cjs => {
(ModuleType::JavaScript, false)
}
MediaType::Jsx => (ModuleType::JavaScript, true),
MediaType::TypeScript
| MediaType::Mts
| MediaType::Cts
| MediaType::Dts
| MediaType::Dmts
| MediaType::Dcts
| MediaType::Tsx => (ModuleType::JavaScript, true),
MediaType::Json => (ModuleType::Json, false),
_ => bail!("Unknown extension {:?}", path.extension()),
};
let code = std::fs::read_to_string(&path)?;
let code = if should_transpile {
let parsed = deno_ast::parse_module(ParseParams {
specifier: module_specifier.clone(),
text: Arc::from(code),
media_type,
capture_tokens: false,
scope_analysis: false,
maybe_syntax: None,
})?;
let res = parsed.transpile(
&TranspileOptions::default(),
&EmitOptions {
source_map: SourceMapOption::Separate,
inline_sources: true,
..Default::default()
},
)?;
let src = res.into_source();
let source_map = src.source_map.unwrap();
let source = src.source;
source_maps
.0
.borrow_mut()
.insert(module_specifier.to_string(), source_map);
String::from_utf8(source).unwrap()
} else {
code
};
Ok(ModuleSource::new(
module_type,
ModuleSourceCode::String(code.into()),
module_specifier,
None,
))
}
ModuleLoadResponse::Sync(load(source_maps, module_specifier))
}
}
pub fn run_plugin_deno_block(
plugin_index_file: &str,
fn_name: &str,
fn_args: Vec<serde_json::Value>,
) -> Result<serde_json::Value, Error> {
block_in_place(|| {
tauri::async_runtime::block_on(run_plugin_deno_2(plugin_index_file, fn_name, fn_args))
})
}
deno_core::extension!(
yaak_runtime,
ops = [ op_yaml_parse ],
esm_entry_point = "ext:yaak_runtime/yaml.js",
esm = [dir "src/plugin-runtime", "yaml.js"]
);
async fn run_plugin_deno_2(
plugin_index_file: &str,
fn_name: &str,
fn_args: Vec<serde_json::Value>,
) -> Result<serde_json::Value, Error> {
let source_map_store = SourceMapStore(Rc::new(RefCell::new(HashMap::new())));
let mut ext_console = deno_console::deno_console::init_ops_and_esm();
ext_console.esm_entry_point = Some("ext:deno_console/01_console.js");
let ext_yaak = yaak_runtime::init_ops_and_esm();
let mut js_runtime = JsRuntime::new(RuntimeOptions {
module_loader: Some(Rc::new(TypescriptModuleLoader {
source_maps: source_map_store.clone(),
})),
source_map_getter: Some(Rc::new(source_map_store)),
extensions: vec![ext_console, ext_yaak],
..Default::default()
});
let main_module = resolve_path(
plugin_index_file,
&std::env::current_dir().context("Unable to get CWD")?,
)?;
// Load the main module so we can do stuff with it
let mod_id = js_runtime.load_main_es_module(&main_module).await?;
let result = js_runtime.mod_evaluate(mod_id);
js_runtime.run_event_loop(Default::default()).await?;
result.await?;
let module_namespace = js_runtime.get_module_namespace(mod_id).unwrap();
let scope = &mut js_runtime.handle_scope();
let module_namespace = v8::Local::<v8::Object>::new(scope, module_namespace);
// Get the exported function we're calling
let func_key = v8::String::new(scope, fn_name).unwrap();
let func = module_namespace.get(scope, func_key.into()).unwrap();
let func = v8::Local::<v8::Function>::try_from(func).unwrap();
let tc_scope = &mut v8::TryCatch::new(scope);
// Create Yaak context object
let null = v8::null(tc_scope).into();
let name = v8::String::new(tc_scope, "foo").unwrap().into();
let value = v8::String::new(tc_scope, "bar").unwrap().into();
let yaak_ctx: v8::Local<v8::Value> =
v8::Object::with_prototype_and_properties(tc_scope, null, &[name], &[value]).into();
// Create the function arguments
let passed_args = &mut fn_args
.iter()
.map(|a| {
let v: v8::Local<v8::Value> = deno_core::serde_v8::to_v8(tc_scope, a).unwrap();
v
})
.collect::<Vec<v8::Local<v8::Value>>>();
let all_args = &mut vec![yaak_ctx];
all_args.append(passed_args);
// Call the function
let func_res = func.call(tc_scope, module_namespace.into(), all_args);
// Catch and return any thrown errors
if tc_scope.has_caught() {
let e = tc_scope.exception().unwrap();
let js_error = JsError::from_v8_exception(tc_scope, e);
return Err(Error::msg(js_error.stack.unwrap_or_default()));
}
// Handle the result
match func_res {
None => Ok(serde_json::Value::Null),
Some(res) => {
if res.is_null() || res.is_undefined() {
Ok(serde_json::Value::Null)
} else {
let value: serde_json::Value = deno_core::serde_v8::from_v8(tc_scope, res).unwrap();
Ok(value)
}
}
}
}

View File

@@ -1,16 +0,0 @@
use deno_core::error::AnyError;
use deno_core::op2;
#[op2]
#[serde]
pub fn op_yaml_parse(#[string] text: String) -> Result<serde_json::Value, AnyError> {
let value = serde_yaml::from_str(&text)?;
Ok(value)
}
#[op2]
#[string]
pub fn op_yaml_stringify(#[serde] value: serde_json::Value) -> Result<String, AnyError> {
let value = serde_yaml::to_string(&value)?;
Ok(value)
}

View File

@@ -8,10 +8,9 @@ use std::time::Duration;
use base64::Engine;
use http::header::{ACCEPT, USER_AGENT};
use http::{HeaderMap, HeaderName, HeaderValue};
use http::{HeaderMap, HeaderName, HeaderValue, Method};
use log::{error, info, warn};
use reqwest::redirect::Policy;
use reqwest::Method;
use reqwest::{multipart, Url};
use sqlx::types::{Json, JsonValue};
use tauri::{Manager, WebviewWindow};
@@ -46,7 +45,6 @@ pub async fn send_http_request(
true => Policy::limited(10), // TODO: Handle redirects natively
false => Policy::none(),
})
.connection_verbose(true)
.gzip(true)
.brotli(true)
.deflate(true)
@@ -108,7 +106,7 @@ pub async fn send_http_request(
format!("Failed to parse URL \"{}\": {}", url_string, e.to_string()),
window,
)
.await;
.await;
}
};
@@ -394,11 +392,11 @@ pub async fn send_http_request(
response.url = v.url().to_string();
response.remote_addr = v.remote_addr().map(|a| a.to_string());
response.version = match v.version() {
reqwest::Version::HTTP_09 => Some("HTTP/0.9".to_string()),
reqwest::Version::HTTP_10 => Some("HTTP/1.0".to_string()),
reqwest::Version::HTTP_11 => Some("HTTP/1.1".to_string()),
reqwest::Version::HTTP_2 => Some("HTTP/2".to_string()),
reqwest::Version::HTTP_3 => Some("HTTP/3".to_string()),
http::Version::HTTP_09 => Some("HTTP/0.9".to_string()),
http::Version::HTTP_10 => Some("HTTP/1.0".to_string()),
http::Version::HTTP_11 => Some("HTTP/1.1".to_string()),
http::Version::HTTP_2 => Some("HTTP/2".to_string()),
http::Version::HTTP_3 => Some("HTTP/3".to_string()),
_ => None,
};

View File

@@ -12,6 +12,8 @@ use std::process::exit;
use std::str::FromStr;
use std::time::Duration;
use ::http::uri::InvalidUri;
use ::http::Uri;
use base64::Engine;
use fern::colors::ColoredLevelConfig;
use log::{debug, error, info, warn};
@@ -59,8 +61,6 @@ use crate::updates::{UpdateMode, YaakUpdater};
use crate::window_menu::app_menu;
mod analytics;
mod deno;
mod deno_ops;
mod grpc;
mod http_request;
mod models;
@@ -69,6 +69,8 @@ mod plugin;
mod render;
#[cfg(target_os = "macos")]
mod tauri_plugin_mac_window;
#[cfg(target_os = "windows")]
mod tauri_plugin_windows_window;
mod updates;
mod window_menu;
@@ -118,6 +120,7 @@ async fn cmd_dismiss_notification(
notification_id: &str,
yaak_notifier: State<'_, Mutex<YaakNotifier>>,
) -> Result<(), String> {
info!("SEEN? {notification_id}");
yaak_notifier.lock().await.seen(&app, notification_id).await
}
@@ -131,14 +134,14 @@ async fn cmd_grpc_reflect(
let req = get_grpc_request(&window, request_id)
.await
.map_err(|e| e.to_string())?;
let uri = safe_uri(req.url.as_str());
let uri = safe_uri(&req.url).map_err(|e| e.to_string())?;
if proto_files.len() > 0 {
grpc_handle
.lock()
.await
.services_from_files(
&req.id,
uri.as_str(),
&uri,
proto_files
.iter()
.map(|p| PathBuf::from_str(p).unwrap())
@@ -149,7 +152,7 @@ async fn cmd_grpc_reflect(
grpc_handle
.lock()
.await
.services_from_reflection(&req.id, uri.as_str())
.services_from_reflection(&req.id, &uri)
.await
}
}
@@ -248,7 +251,7 @@ async fn cmd_grpc_go(
let maybe_in_msg_tx = std::sync::Mutex::new(Some(in_msg_tx.clone()));
let (cancelled_tx, mut cancelled_rx) = tokio::sync::watch::channel(false);
let uri = safe_uri(&req.url);
let uri = safe_uri(&req.url).map_err(|e| e.to_string())?;
let in_msg_stream = tokio_stream::wrappers::ReceiverStream::new(in_msg_rx);
@@ -266,7 +269,7 @@ async fn cmd_grpc_go(
.await
.connect(
&req.clone().id,
uri.as_str(),
uri,
proto_files
.iter()
.map(|p| PathBuf::from_str(p).unwrap())
@@ -754,8 +757,8 @@ async fn cmd_import_data(
"importer-yaak",
"importer-curl",
];
let file =
read_to_string(file_path).unwrap_or_else(|_| panic!("Unable to read file {}", file_path));
let file = read_to_string(file_path)
.unwrap_or_else(|_| panic!("Unable to read file {}", file_path));
let file_contents = file.as_str();
for plugin_name in plugins {
let v = run_plugin_import(&w.app_handle(), plugin_name, file_contents)
@@ -807,15 +810,13 @@ async fn cmd_import_data(
}
};
info!("Importing resources");
for mut v in r.resources.workspaces {
v.id = maybe_gen_id(v.id.as_str(), ModelType::TypeWorkspace, &mut id_map);
let x = upsert_workspace(&w, v).await.map_err(|e| e.to_string())?;
imported_resources.workspaces.push(x.clone());
info!("Imported workspace: {}", x.name);
}
info!(
"Imported {} workspaces",
imported_resources.workspaces.len()
);
for mut v in r.resources.environments {
v.id = maybe_gen_id(v.id.as_str(), ModelType::TypeEnvironment, &mut id_map);
@@ -826,11 +827,8 @@ async fn cmd_import_data(
);
let x = upsert_environment(&w, v).await.map_err(|e| e.to_string())?;
imported_resources.environments.push(x.clone());
info!("Imported environment: {}", x.name);
}
info!(
"Imported {} environments",
imported_resources.environments.len()
);
for mut v in r.resources.folders {
v.id = maybe_gen_id(v.id.as_str(), ModelType::TypeFolder, &mut id_map);
@@ -842,8 +840,8 @@ async fn cmd_import_data(
v.folder_id = maybe_gen_id_opt(v.folder_id, ModelType::TypeFolder, &mut id_map);
let x = upsert_folder(&w, v).await.map_err(|e| e.to_string())?;
imported_resources.folders.push(x.clone());
info!("Imported folder: {}", x.name);
}
info!("Imported {} folders", imported_resources.folders.len());
for mut v in r.resources.http_requests {
v.id = maybe_gen_id(v.id.as_str(), ModelType::TypeHttpRequest, &mut id_map);
@@ -857,11 +855,8 @@ async fn cmd_import_data(
.await
.map_err(|e| e.to_string())?;
imported_resources.http_requests.push(x.clone());
info!("Imported request: {}", x.name);
}
info!(
"Imported {} http_requests",
imported_resources.http_requests.len()
);
for mut v in r.resources.grpc_requests {
v.id = maybe_gen_id(v.id.as_str(), ModelType::TypeGrpcRequest, &mut id_map);
@@ -875,11 +870,8 @@ async fn cmd_import_data(
.await
.map_err(|e| e.to_string())?;
imported_resources.grpc_requests.push(x.clone());
info!("Imported request: {}", x.name);
}
info!(
"Imported {} grpc_requests",
imported_resources.grpc_requests.len()
);
Ok(imported_resources)
}
@@ -907,8 +899,12 @@ async fn cmd_request_to_curl(
}
#[tauri::command]
async fn cmd_curl_to_request(app_handle: AppHandle, command: &str, workspace_id: &str) -> Result<HttpRequest, String> {
let v = run_plugin_import(&app_handle, "importer-curl", command)
async fn cmd_curl_to_request(
app: AppHandle,
command: &str,
workspace_id: &str,
) -> Result<HttpRequest, String> {
let v = run_plugin_import(&app, "importer-curl", command)
.await
.map_err(|e| e.to_string());
match v {
@@ -958,28 +954,6 @@ async fn cmd_export_data(
Ok(())
}
#[tauri::command]
async fn cmd_save_response(
window: WebviewWindow,
response_id: &str,
filepath: &str,
) -> Result<(), String> {
let response = get_http_response(&window, response_id)
.await
.map_err(|e| e.to_string())?;
let body_path = match response.body_path {
None => {
return Err("Response does not have a body".to_string());
}
Some(p) => p,
};
fs::copy(body_path, filepath).map_err(|e| e.to_string())?;
Ok(())
}
#[tauri::command]
async fn cmd_send_http_request(
window: WebviewWindow,
@@ -1060,7 +1034,6 @@ async fn response_err(
error: String,
w: &WebviewWindow,
) -> Result<HttpResponse, String> {
warn!("Failed to send request: {}", error);
let mut response = response.clone();
response.elapsed = -1;
response.error = Some(error.clone());
@@ -1520,8 +1493,6 @@ async fn cmd_list_workspaces(w: WebviewWindow) -> Result<Vec<Workspace>, String>
&w,
Workspace {
name: "Yaak".to_string(),
setting_follow_redirects: true,
setting_validate_certificates: true,
..Default::default()
},
)
@@ -1582,6 +1553,11 @@ pub fn run() {
.plugin(tauri_plugin_os::init())
.plugin(tauri_plugin_fs::init());
#[cfg(target_os = "windows")]
{
builder = builder.plugin(tauri_plugin_windows_window::init());
}
#[cfg(target_os = "macos")]
{
builder = builder.plugin(tauri_plugin_mac_window::init());
@@ -1610,9 +1586,7 @@ pub fn run() {
.level_for("tokio_util", log::LevelFilter::Info)
.level_for("tonic", log::LevelFilter::Info)
.level_for("tower", log::LevelFilter::Info)
.level_for("tracing", log::LevelFilter::Warn)
.level_for("swc_ecma_codegen", log::LevelFilter::Off)
.level_for("swc_ecma_transforms_base", log::LevelFilter::Off)
.level_for("tracing", log::LevelFilter::Info)
.with_colors(ColoredLevelConfig::default())
.level(if is_dev() {
log::LevelFilter::Trace
@@ -1724,7 +1698,6 @@ pub fn run() {
cmd_new_window,
cmd_request_to_curl,
cmd_dismiss_notification,
cmd_save_response,
cmd_send_ephemeral_request,
cmd_send_http_request,
cmd_set_key_value,
@@ -1926,10 +1899,11 @@ async fn get_update_mode(h: &AppHandle) -> UpdateMode {
UpdateMode::new(settings.update_channel.as_str())
}
fn safe_uri(endpoint: &str) -> String {
if endpoint.starts_with("http://") || endpoint.starts_with("https://") {
endpoint.into()
fn safe_uri(endpoint: &str) -> Result<Uri, InvalidUri> {
let uri = if endpoint.starts_with("http://") || endpoint.starts_with("https://") {
Uri::from_str(endpoint)?
} else {
format!("http://{}", endpoint)
}
Uri::from_str(&format!("http://{}", endpoint))?
};
Ok(uri)
}

View File

@@ -59,7 +59,6 @@ pub struct Settings {
pub interface_scale: i64,
pub editor_font_size: i64,
pub editor_soft_wrap: bool,
pub open_workspace_new_window: Option<bool>,
}
#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize, Default)]
@@ -892,8 +891,7 @@ async fn get_settings(mgr: &impl Manager<Wry>) -> Result<Settings, sqlx::Error>
SELECT
id, model, created_at, updated_at, theme, appearance,
theme_dark, theme_light, update_channel,
interface_font_size, interface_scale, editor_font_size, editor_soft_wrap,
open_workspace_new_window
interface_font_size, interface_scale, editor_font_size, editor_soft_wrap
FROM settings
WHERE id = 'default'
"#,
@@ -930,9 +928,8 @@ pub async fn update_settings(
r#"
UPDATE settings SET (
theme, appearance, theme_dark, theme_light, update_channel,
interface_font_size, interface_scale, editor_font_size, editor_soft_wrap,
open_workspace_new_window
) = (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) WHERE id = 'default';
interface_font_size, interface_scale, editor_font_size, editor_soft_wrap
) = (?, ?, ?, ?, ?, ?, ?, ?, ?) WHERE id = 'default';
"#,
settings.theme,
settings.appearance,
@@ -943,7 +940,6 @@ pub async fn update_settings(
settings.interface_scale,
settings.editor_font_size,
settings.editor_soft_wrap,
settings.open_workspace_new_window,
)
.execute(&db)
.await?;

View File

@@ -1,8 +1,8 @@
use std::time::SystemTime;
use chrono::{Duration, NaiveDateTime, Utc};
use http::Method;
use log::debug;
use reqwest::Method;
use serde::{Deserialize, Serialize};
use tauri::{AppHandle, Manager};

View File

@@ -1,7 +0,0 @@
((globalThis) => {
const core = Deno.core;
globalThis.YAML = {
parse: core.ops.op_yaml_parse,
stringify: core.ops.op_yaml_stringify,
};
})(globalThis);

View File

@@ -1,11 +1,17 @@
use std::path;
use std::rc::Rc;
use log::error;
use boa_engine::builtins::promise::PromiseState;
use boa_engine::{
js_string, module::SimpleModuleLoader, property::Attribute, Context, JsNativeError, JsValue,
Module, Source,
};
use boa_runtime::Console;
use log::{debug, error};
use serde::{Deserialize, Serialize};
use serde_json::json;
use tauri::path::BaseDirectory;
use tauri::{AppHandle, Manager};
use crate::deno::run_plugin_deno_block;
use crate::models::{HttpRequest, WorkspaceExportResources};
#[derive(Default, Debug, Deserialize, Serialize)]
@@ -24,31 +30,20 @@ pub async fn run_plugin_filter(
response_body: &str,
filter: &str,
) -> Option<FilterResult> {
let plugin_dir = app_handle
.path()
.resolve("plugins", BaseDirectory::Resource)
.expect("failed to resolve plugin directory resource")
.join(plugin_name);
let plugin_index_file = plugin_dir.join("index.mjs");
let result = run_plugin_deno_block(
plugin_index_file.to_str().unwrap(),
let result_json = run_plugin(
app_handle,
plugin_name,
"pluginHookResponseFilter",
vec![
serde_json::to_value(response_body).unwrap(),
serde_json::to_value(filter).unwrap(),
],
)
.map_err(|e| e.to_string())
.expect("Failed to run plugin");
&[js_string!(response_body).into(), js_string!(filter).into()],
);
if result.is_null() {
if result_json.is_null() {
error!("Plugin {} failed to run", plugin_name);
return None;
}
let resources: FilterResult =
serde_json::from_value(result).expect("failed to parse filter plugin result json");
serde_json::from_value(result_json).expect("failed to parse filter plugin result json");
Some(resources)
}
@@ -56,23 +51,17 @@ pub fn run_plugin_export_curl(
app_handle: &AppHandle,
request: &HttpRequest,
) -> Result<String, String> {
let plugin_dir = app_handle
.path()
.resolve("plugins", BaseDirectory::Resource)
.expect("failed to resolve plugin directory resource")
.join("exporter-curl");
let plugin_index_file = plugin_dir.join("index.mjs");
let mut context = Context::default();
let request_json = serde_json::to_value(request).map_err(|e| e.to_string())?;
let result = run_plugin_deno_block(
plugin_index_file.to_str().unwrap(),
let result_json = run_plugin(
app_handle,
"exporter-curl",
"pluginHookExport",
vec![request_json],
)
.map_err(|e| e.to_string())?;
&[JsValue::from_json(&request_json, &mut context).map_err(|e| e.to_string())?],
);
let export_str: String = serde_json::from_value(result).map_err(|e| e.to_string())?;
Ok(export_str)
let resources: String = serde_json::from_value(result_json).map_err(|e| e.to_string())?;
Ok(resources)
}
pub async fn run_plugin_import(
@@ -80,6 +69,27 @@ pub async fn run_plugin_import(
plugin_name: &str,
file_contents: &str,
) -> Result<Option<ImportResult>, String> {
let result_json = run_plugin(
app_handle,
plugin_name,
"pluginHookImport",
&[js_string!(file_contents).into()],
);
if result_json.is_null() {
return Ok(None);
}
let resources: ImportResult = serde_json::from_value(result_json).map_err(|e| e.to_string())?;
Ok(Some(resources))
}
fn run_plugin(
app_handle: &AppHandle,
plugin_name: &str,
entrypoint: &str,
js_args: &[JsValue],
) -> serde_json::Value {
let plugin_dir = app_handle
.path()
.resolve("plugins", BaseDirectory::Resource)
@@ -87,17 +97,68 @@ pub async fn run_plugin_import(
.join(plugin_name);
let plugin_index_file = plugin_dir.join("index.mjs");
let result = run_plugin_deno_block(
plugin_index_file.to_str().unwrap(),
"pluginHookImport",
vec![serde_json::to_value(file_contents).map_err(|e| e.to_string())?],
)
.map_err(|e| e.to_string())?;
debug!(
"Running plugin dir={:?} file={:?}",
plugin_dir, plugin_index_file
);
if result.is_null() {
return Ok(None);
let loader = Rc::new(SimpleModuleLoader::new(plugin_dir).unwrap());
let context = &mut Context::builder()
.module_loader(loader.clone())
.build()
.expect("failed to create context");
add_runtime(context);
let source = Source::from_filepath(&plugin_index_file).expect("Error opening file");
// Can also pass a `Some(realm)` if you need to execute the module in another realm.
let module = Module::parse(source, None, context).expect("failed to parse module");
// Insert parsed entrypoint into the module loader
loader.insert(plugin_index_file, module.clone());
let promise_result = module.load_link_evaluate(context);
// Very important to push forward the job queue after queueing promises.
context.run_jobs();
// Checking if the final promise didn't return an error.
match promise_result.state() {
PromiseState::Pending => {
panic!("Promise was pending");
}
PromiseState::Fulfilled(v) => {
assert_eq!(v, JsValue::undefined())
}
PromiseState::Rejected(err) => {
panic!("Failed to link: {}", err.display());
}
}
let resources: ImportResult = serde_json::from_value(result).map_err(|e| e.to_string())?;
Ok(Some(resources))
let namespace = module.namespace(context);
let result = namespace
.get(js_string!(entrypoint), context)
.expect("failed to get entrypoint")
.as_callable()
.cloned()
.ok_or_else(|| JsNativeError::typ().with_message("export wasn't a function!"))
.expect("Failed to get entrypoint")
.call(&JsValue::undefined(), js_args, context)
.expect("Failed to call entrypoint");
match result.is_undefined() {
true => json!(null), // to_json doesn't work with undefined (yet)
false => result
.to_json(context)
.expect("failed to convert result to json"),
}
}
fn add_runtime(context: &mut Context) {
let console = Console::init(context);
context
.register_global_property(js_string!(Console::NAME), console, Attribute::all())
.expect("the console builtin shouldn't exist");
}

View File

@@ -1,11 +1,9 @@
use std::collections::HashMap;
use regex::Regex;
use sqlx::types::{Json, JsonValue};
use crate::models::{
Environment, EnvironmentVariable, HttpRequest, HttpRequestHeader, HttpUrlParameter, Workspace,
};
use templates::parse_and_render;
use crate::models::{Environment, HttpRequest, HttpRequestHeader, HttpUrlParameter, Workspace};
pub fn render_request(r: &HttpRequest, w: &Workspace, e: Option<&Environment>) -> HttpRequest {
let r = r.clone();
@@ -66,29 +64,30 @@ pub fn render_request(r: &HttpRequest, w: &Workspace, e: Option<&Environment>) -
}
pub fn render(template: &str, workspace: &Workspace, environment: Option<&Environment>) -> String {
let mut variables = HashMap::new();
variables = add_variable_to_map(variables, &workspace.variables.0);
if let Some(e) = environment {
variables = add_variable_to_map(variables, &e.variables.0);
}
parse_and_render(template, variables, None)
}
fn add_variable_to_map<'a>(
m: HashMap<&'a str, &'a str>,
variables: &'a Vec<EnvironmentVariable>,
) -> HashMap<&'a str, &'a str> {
let mut map = m.clone();
for variable in variables {
let mut map = HashMap::new();
let workspace_variables = &workspace.variables.0;
for variable in workspace_variables {
if !variable.enabled || variable.value.is_empty() {
continue;
}
let name = variable.name.as_str();
let value = variable.value.as_str();
map.insert(name, value);
map.insert(variable.name.as_str(), variable.value.as_str());
}
map
if let Some(e) = environment {
let environment_variables = &e.variables.0;
for variable in environment_variables {
if !variable.enabled || variable.value.is_empty() {
continue;
}
map.insert(variable.name.as_str(), variable.value.as_str());
}
}
Regex::new(r"\$\{\[\s*([^]\s]+)\s*]}")
.expect("Failed to create regex")
.replace_all(template, |caps: &regex::Captures| {
let key = caps.get(1).unwrap().as_str();
map.get(key).unwrap_or(&"")
})
.to_string()
}

View File

@@ -1,5 +1,4 @@
use hex_color::HexColor;
use log::warn;
use objc::{msg_send, sel, sel_impl};
use rand::{distributions::Alphanumeric, Rng};
use tauri::{
@@ -26,35 +25,15 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
let window_for_theme = window.clone();
let id1 = h.listen("yaak_bg_changed", move |ev| {
let color_str: String = match serde_json::from_str(ev.payload()) {
Ok(color) => color,
Err(err) => {
warn!("Failed to JSON parse color '{}': {}", ev.payload(), err);
return;
}
};
match HexColor::parse_rgb(color_str.trim()) {
Ok(color) => {
update_window_theme(window_for_theme.clone(), color);
}
Err(err) => {
warn!("Failed to parse background color '{}': {}", color_str, err)
}
}
let payload = serde_json::from_str::<&str>(ev.payload()).unwrap().trim();
let color = HexColor::parse_rgb(payload).unwrap();
update_window_theme(window_for_theme.clone(), color);
});
let window_for_title = window.clone();
let id2 = h.listen("yaak_title_changed", move |ev| {
let title: String = match serde_json::from_str(ev.payload()) {
Ok(title) => title,
Err(err) => {
warn!("Failed to parse window title \"{}\": {}", ev.payload(), err);
return;
}
};
update_window_title(window_for_title.clone(), title);
let payload = serde_json::from_str::<&str>(ev.payload()).unwrap().trim();
update_window_title(window_for_title.clone(), payload.to_string());
});
let h = h.clone();

View File

@@ -0,0 +1,97 @@
use hex_color::HexColor;
use tauri::{Manager, Runtime, Window, WindowEvent};
use std::mem::transmute;
use std::{ffi::c_void, mem::size_of, ptr};
use tauri::plugin::{Builder, TauriPlugin};
use windows::Win32::UI::Controls::{
WTA_NONCLIENT, WTNCA_NODRAWICON, WTNCA_NOMIRRORHELP, WTNCA_NOSYSMENU,
};
use windows::Win32::Foundation::COLORREF;
use windows::Win32::Foundation::{BOOL, HWND};
use windows::Win32::Graphics::Dwm::DwmSetWindowAttribute;
use windows::Win32::Graphics::Dwm::DWMWA_CAPTION_COLOR;
use windows::Win32::Graphics::Dwm::DWMWA_USE_IMMERSIVE_DARK_MODE;
use windows::Win32::UI::Controls::SetWindowThemeAttribute;
use windows::Win32::UI::Controls::WTNCA_NODRAWCAPTION;
pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("windows_window")
.on_window_ready(|window| {
#[cfg(target_os = "windows")]
setup_win_window(window);
return;
})
.build()
}
fn hex_color_to_colorref(color: HexColor) -> COLORREF {
// TODO: Remove this unsafe, This operation doesn't need to be unsafe!
unsafe { COLORREF(transmute::<[u8; 4], u32>([color.r, color.g, color.b, 0])) }
}
struct WinThemeAttribute {
#[allow(dead_code)]
flag: u32,
#[allow(dead_code)]
mask: u32,
}
#[cfg(target_os = "windows")]
fn update_bg_color(hwnd: &HWND, bg_color: HexColor) {
let use_dark_mode = BOOL::from(true);
let final_color = hex_color_to_colorref(bg_color);
unsafe {
DwmSetWindowAttribute(
HWND(hwnd.0),
DWMWA_USE_IMMERSIVE_DARK_MODE,
ptr::addr_of!(use_dark_mode) as *const c_void,
size_of::<BOOL>().try_into().unwrap(),
)
.unwrap();
DwmSetWindowAttribute(
HWND(hwnd.0),
DWMWA_CAPTION_COLOR,
ptr::addr_of!(final_color) as *const c_void,
size_of::<COLORREF>().try_into().unwrap(),
)
.unwrap();
let flags = WTNCA_NODRAWCAPTION | WTNCA_NODRAWICON;
let mask = WTNCA_NODRAWCAPTION | WTNCA_NODRAWICON | WTNCA_NOSYSMENU | WTNCA_NOMIRRORHELP;
let options = WinThemeAttribute { flag: flags, mask };
SetWindowThemeAttribute(
HWND(hwnd.0),
WTA_NONCLIENT,
ptr::addr_of!(options) as *const c_void,
size_of::<WinThemeAttribute>().try_into().unwrap(),
)
.unwrap();
}
}
#[cfg(target_os = "windows")]
pub fn setup_win_window<R: Runtime>(window: Window<R>) {
let win_handle = window.hwnd().unwrap();
let win_clone = win_handle.clone();
let event_id = window.listen("yaak_bg_changed", move |ev| {
let payload = serde_json::from_str::<&str>(ev.payload()).unwrap().trim();
let color = HexColor::parse_rgb(payload).unwrap();
update_bg_color(&HWND(win_clone.0), color);
});
let h = window.app_handle().clone();
window.on_window_event(move |e| match e {
WindowEvent::Destroyed => {
h.unlisten(event_id);
}
_ => {}
})
}

View File

@@ -134,11 +134,8 @@ pub fn app_menu(app_handle: &AppHandle) -> tauri::Result<Menu<Wry>> {
.build(app_handle)?,
&MenuItemBuilder::with_id("dev.reset_size".to_string(), "Reset Size")
.build(app_handle)?,
&MenuItemBuilder::with_id(
"dev.generate_theme_css".to_string(),
"Generate Theme CSS",
)
.build(app_handle)?,
&MenuItemBuilder::with_id("dev.generate_theme_css".to_string(), "Generate Theme CSS")
.build(app_handle)?,
],
)?,
],

View File

@@ -1,13 +1,13 @@
{
"productName": "yaak",
"version": "2024.6.4",
"identifier": "app.yaak.desktop",
"build": {
"beforeBuildCommand": "npm run build",
"beforeDevCommand": "npm run dev",
"devUrl": "http://localhost:1420",
"frontendDist": "../dist"
},
"productName": "Yaak",
"version": "2024.5.0",
"identifier": "app.yaak.desktop",
"app": {
"withGlobalTauri": false,
"security": {
@@ -27,7 +27,6 @@
"desktop": {
"schemes": [
"yaak"
]
}
},

View File

@@ -1,6 +0,0 @@
[package]
name = "templates"
version = "0.1.0"
edition = "2021"
[dependencies]

View File

@@ -1,7 +0,0 @@
pub mod parser;
pub mod renderer;
pub use parser::*;
pub use renderer::*;
pub fn template_foo() {}

View File

@@ -1,413 +0,0 @@
#[derive(Clone, PartialEq, Debug)]
pub enum Val {
Str(String),
Var(String),
Fn { name: String, args: Vec<Val> },
}
#[derive(Clone, PartialEq, Debug)]
pub enum Token {
Raw(String),
Tag(Val),
Eof,
}
// Template Syntax
//
// ${[ my_var ]}
// ${[ my_fn() ]}
// ${[ my_fn(my_var) ]}
// ${[ my_fn(my_var, "A String") ]}
// default
#[derive(Default)]
pub struct Parser {
tokens: Vec<Token>,
chars: Vec<char>,
pos: usize,
curr_text: String,
}
impl Parser {
pub fn new(text: &str) -> Parser {
Parser {
chars: text.chars().collect(),
..Parser::default()
}
}
pub fn parse(&mut self) -> Vec<Token> {
let start_pos = self.pos;
while self.pos < self.chars.len() {
if self.match_str("${[") {
let start_curr = self.pos;
if let Some(t) = self.parse_tag() {
self.push_token(t);
} else {
self.pos = start_curr;
self.curr_text += "${[";
}
} else {
let ch = self.next_char();
self.curr_text.push(ch);
}
if start_pos == self.pos {
panic!("Parser stuck!");
}
}
self.push_token(Token::Eof);
self.tokens.clone()
}
fn parse_tag(&mut self) -> Option<Token> {
// Parse up to first identifier
// ${[ my_var...
self.skip_whitespace();
let val = match self.parse_value() {
Some(v) => v,
None => return None,
};
// Parse to closing tag
// ${[ my_var(a, b, c) ]}
self.skip_whitespace();
if !self.match_str("]}") {
return None;
}
Some(Token::Tag(val))
}
#[allow(dead_code)]
fn debug_pos(&self, x: &str) {
println!(
r#"Position: {x} -- [{}] = {} --> "{}" --> {:?}"#,
self.pos,
self.chars[self.pos],
self.chars.iter().collect::<String>(),
self.tokens,
);
}
fn parse_value(&mut self) -> Option<Val> {
if let Some((name, args)) = self.parse_fn() {
Some(Val::Fn { name, args })
} else if let Some(v) = self.parse_ident() {
Some(Val::Var(v))
} else if let Some(v) = self.parse_string() {
Some(Val::Str(v))
} else {
None
}
}
fn parse_fn(&mut self) -> Option<(String, Vec<Val>)> {
let start_pos = self.pos;
let name = match self.parse_ident() {
Some(v) => v,
None => {
self.pos = start_pos;
return None;
}
};
let args = match self.parse_fn_args() {
Some(args) => args,
None => {
self.pos = start_pos;
return None;
}
};
Some((name, args))
}
fn parse_fn_args(&mut self) -> Option<Vec<Val>> {
if !self.match_str("(") {
return None;
}
let start_pos = self.pos;
let mut args: Vec<Val> = Vec::new();
while self.pos < self.chars.len() {
self.skip_whitespace();
if let Some(v) = self.parse_value() {
args.push(v);
}
self.skip_whitespace();
if self.match_str(")") {
break;
}
self.skip_whitespace();
// If we don't find a comma, that's bad
if !args.is_empty() && !self.match_str(",") {
self.pos = start_pos;
return None;
}
if start_pos == self.pos {
panic!("Parser stuck!");
}
}
return Some(args);
}
fn parse_ident(&mut self) -> Option<String> {
let start_pos = self.pos;
let mut text = String::new();
while self.pos < self.chars.len() {
let ch = self.peek_char();
if ch.is_alphanumeric() || ch == '_' {
text.push(ch);
self.pos += 1;
} else {
break;
}
if start_pos == self.pos {
panic!("Parser stuck!");
}
}
if text.is_empty() {
self.pos = start_pos;
return None;
}
return Some(text);
}
fn parse_string(&mut self) -> Option<String> {
let start_pos = self.pos;
let mut text = String::new();
if !self.match_str("\"") {
return None;
}
let mut found_closing = false;
while self.pos < self.chars.len() {
let ch = self.next_char();
match ch {
'\\' => {
text.push(self.next_char());
}
'"' => {
found_closing = true;
break;
}
_ => {
text.push(ch);
}
}
if start_pos == self.pos {
panic!("Parser stuck!");
}
}
if !found_closing {
self.pos = start_pos;
return None;
}
return Some(text);
}
fn skip_whitespace(&mut self) {
while self.pos < self.chars.len() {
if self.peek_char().is_whitespace() {
self.pos += 1;
} else {
break;
}
}
}
fn next_char(&mut self) -> char {
let ch = self.peek_char();
self.pos += 1;
ch
}
fn peek_char(&self) -> char {
let ch = self.chars[self.pos];
ch
}
fn push_token(&mut self, token: Token) {
// Push any text we've accumulated
if !self.curr_text.is_empty() {
let text_token = Token::Raw(self.curr_text.clone());
self.tokens.push(text_token);
self.curr_text.clear();
}
self.tokens.push(token);
}
fn match_str(&mut self, value: &str) -> bool {
if self.pos + value.len() > self.chars.len() {
return false;
}
let cmp = self.chars[self.pos..self.pos + value.len()]
.iter()
.collect::<String>();
if cmp == value {
// We have a match, so advance the current index
self.pos += value.len();
true
} else {
false
}
}
}
#[cfg(test)]
mod tests {
use crate::*;
#[test]
fn var_simple() {
let mut p = Parser::new("${[ foo ]}");
assert_eq!(
p.parse(),
vec![Token::Tag(Val::Var("foo".into())), Token::Eof]
);
}
#[test]
fn var_multiple_names_invalid() {
let mut p = Parser::new("${[ foo bar ]}");
assert_eq!(
p.parse(),
vec![Token::Raw("${[ foo bar ]}".into()), Token::Eof]
);
}
#[test]
fn tag_string() {
let mut p = Parser::new(r#"${[ "foo \"bar\" baz" ]}"#);
assert_eq!(
p.parse(),
vec![Token::Tag(Val::Str(r#"foo "bar" baz"#.into())), Token::Eof]
);
}
#[test]
fn var_surrounded() {
let mut p = Parser::new("Hello ${[ foo ]}!");
assert_eq!(
p.parse(),
vec![
Token::Raw("Hello ".to_string()),
Token::Tag(Val::Var("foo".into())),
Token::Raw("!".to_string()),
Token::Eof,
]
);
}
#[test]
fn fn_simple() {
let mut p = Parser::new("${[ foo() ]}");
assert_eq!(
p.parse(),
vec![
Token::Tag(Val::Fn {
name: "foo".into(),
args: Vec::new(),
}),
Token::Eof
]
);
}
#[test]
fn fn_ident_arg() {
let mut p = Parser::new("${[ foo(bar) ]}");
assert_eq!(
p.parse(),
vec![
Token::Tag(Val::Fn {
name: "foo".into(),
args: vec![Val::Var("bar".into())],
}),
Token::Eof
]
);
}
#[test]
fn fn_ident_args() {
let mut p = Parser::new("${[ foo(bar,baz, qux ) ]}");
assert_eq!(
p.parse(),
vec![
Token::Tag(Val::Fn {
name: "foo".into(),
args: vec![
Val::Var("bar".into()),
Val::Var("baz".into()),
Val::Var("qux".into()),
],
}),
Token::Eof
]
);
}
#[test]
fn fn_mixed_args() {
let mut p = Parser::new(r#"${[ foo(bar,"baz \"hi\"", qux ) ]}"#);
assert_eq!(
p.parse(),
vec![
Token::Tag(Val::Fn {
name: "foo".into(),
args: vec![
Val::Var("bar".into()),
Val::Str(r#"baz "hi""#.into()),
Val::Var("qux".into()),
],
}),
Token::Eof
]
);
}
#[test]
fn fn_nested() {
let mut p = Parser::new(r#"${[ outer(inner(foo, "i"), "o") ]}"#);
assert_eq!(
p.parse(),
vec![
Token::Tag(Val::Fn {
name: "outer".into(),
args: vec![
Val::Fn {
name: "inner".into(),
args: vec![Val::Var("foo".into()), Val::Str("i".into()),],
},
Val::Str("o".into())
],
}),
Token::Eof
]
);
}
}

View File

@@ -1,132 +0,0 @@
use std::collections::HashMap;
use crate::{Parser, Token, Val};
type TemplateCallback = fn(name: &str, args: Vec<String>) -> String;
pub fn parse_and_render(
template: &str,
vars: HashMap<&str, &str>,
cb: Option<TemplateCallback>,
) -> String {
let mut p = Parser::new(template);
let tokens = p.parse();
render(tokens, vars, cb)
}
pub fn render(
tokens: Vec<Token>,
vars: HashMap<&str, &str>,
cb: Option<TemplateCallback>,
) -> String {
let mut doc_str: Vec<String> = Vec::new();
for t in tokens {
match t {
Token::Raw(s) => doc_str.push(s),
Token::Tag(val) => doc_str.push(render_tag(val, vars.clone(), cb)),
Token::Eof => {}
}
}
return doc_str.join("");
}
fn render_tag<'s>(
val: Val,
vars: HashMap<&'s str, &'s str>,
cb: Option<TemplateCallback>,
) -> String {
match val {
Val::Str(s) => s.into(),
Val::Var(name) => match vars.get(name.as_str()) {
Some(v) => v.to_string(),
None => "".into(),
},
Val::Fn { name, args } => {
let empty = &"";
let resolved_args = args
.iter()
.map(|a| match a {
Val::Str(s) => s.to_string(),
Val::Var(i) => vars.get(i.as_str()).unwrap_or(empty).to_string(),
val => render_tag(val.clone(), vars.clone(), cb),
})
.collect::<Vec<String>>();
match cb {
Some(cb) => cb(name.as_str(), resolved_args),
None => "".into(),
}
}
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use crate::*;
#[test]
fn render_empty() {
let template = "";
let vars = HashMap::new();
let result = "";
assert_eq!(parse_and_render(template, vars, None), result.to_string());
}
#[test]
fn render_text_only() {
let template = "Hello World!";
let vars = HashMap::new();
let result = "Hello World!";
assert_eq!(parse_and_render(template, vars, None), result.to_string());
}
#[test]
fn render_simple() {
let template = "${[ foo ]}";
let vars = HashMap::from([("foo", "bar")]);
let result = "bar";
assert_eq!(parse_and_render(template, vars, None), result.to_string());
}
#[test]
fn render_surrounded() {
let template = "hello ${[ word ]} world!";
let vars = HashMap::from([("word", "cruel")]);
let result = "hello cruel world!";
assert_eq!(parse_and_render(template, vars, None), result.to_string());
}
#[test]
fn render_valid_fn() {
let vars = HashMap::new();
let template = r#"${[ say_hello("John", "Kate") ]}"#;
let result = r#"say_hello: ["John", "Kate"]"#;
fn cb(name: &str, args: Vec<String>) -> String {
format!("{name}: {:?}", args)
}
assert_eq!(parse_and_render(template, vars, Some(cb)), result);
}
#[test]
fn render_nested_fn() {
let vars = HashMap::new();
let template = r#"${[ upper(secret()) ]}"#;
let result = r#"ABC"#;
fn cb(name: &str, args: Vec<String>) -> String {
match name {
"secret" => "abc".to_string(),
"upper" => args[0].to_string().to_uppercase(),
_ => "".to_string(),
}
}
assert_eq!(
parse_and_render(template, vars, Some(cb)),
result.to_string()
);
}
}

View File

@@ -1,243 +1,28 @@
import { invoke } from '@tauri-apps/api/core';
import classNames from 'classnames';
import { search } from 'fast-fuzzy';
import type { KeyboardEvent, ReactNode } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useActiveCookieJar } from '../hooks/useActiveCookieJar';
import { useActiveEnvironment } from '../hooks/useActiveEnvironment';
import type { ReactNode } from 'react';
import { useCallback, useMemo, useState } from 'react';
import { useActiveEnvironmentId } from '../hooks/useActiveEnvironmentId';
import { useActiveRequestId } from '../hooks/useActiveRequestId';
import { useActiveWorkspaceId } from '../hooks/useActiveWorkspaceId';
import { useAppRoutes } from '../hooks/useAppRoutes';
import { useCreateEnvironment } from '../hooks/useCreateEnvironment';
import { useCreateGrpcRequest } from '../hooks/useCreateGrpcRequest';
import { useCreateHttpRequest } from '../hooks/useCreateHttpRequest';
import { useCreateWorkspace } from '../hooks/useCreateWorkspace';
import { useDebouncedState } from '../hooks/useDebouncedState';
import { useEnvironments } from '../hooks/useEnvironments';
import type { HotkeyAction } from '../hooks/useHotKey';
import { useHotKey } from '../hooks/useHotKey';
import { useOpenWorkspace } from '../hooks/useOpenWorkspace';
import { useRecentEnvironments } from '../hooks/useRecentEnvironments';
import { useRecentRequests } from '../hooks/useRecentRequests';
import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces';
import { getRecentEnvironments } from '../hooks/useRecentEnvironments';
import { useRequests } from '../hooks/useRequests';
import { useSidebarHidden } from '../hooks/useSidebarHidden';
import { useWorkspaces } from '../hooks/useWorkspaces';
import { fallbackRequestName } from '../lib/fallbackRequestName';
import { CookieDialog } from './CookieDialog';
import { Button } from './core/Button';
import { Heading } from './core/Heading';
import { HotKey } from './core/HotKey';
import { HttpMethodTag } from './core/HttpMethodTag';
import { Icon } from './core/Icon';
import { PlainInput } from './core/PlainInput';
import { HStack } from './core/Stacks';
import { useDialog } from './DialogContext';
import { EnvironmentEditDialog } from './EnvironmentEditDialog';
interface CommandPaletteGroup {
key: string;
label: ReactNode;
items: CommandPaletteItem[];
}
type CommandPaletteItem = {
key: string;
onSelect: () => void;
action?: HotkeyAction;
} & ({ searchText: string; label: ReactNode } | { label: string });
const MAX_PER_GROUP = 8;
import { Input } from './core/Input';
export function CommandPalette({ onClose }: { onClose: () => void }) {
const [command, setCommand] = useDebouncedState<string>('', 150);
const [selectedItemKey, setSelectedItemKey] = useState<string | null>(null);
const [selectedIndex, setSelectedIndex] = useState<number>(0);
const routes = useAppRoutes();
const activeEnvironmentId = useActiveEnvironmentId();
const activeRequestId = useActiveRequestId();
const active = useActiveWorkspaceId();
const workspaces = useWorkspaces();
const environments = useEnvironments();
const recentEnvironments = useRecentEnvironments();
const recentWorkspaces = useRecentWorkspaces();
const requests = useRequests();
const recentRequests = useRecentRequests();
const openWorkspace = useOpenWorkspace();
const createWorkspace = useCreateWorkspace();
const createHttpRequest = useCreateHttpRequest();
const { activeCookieJar } = useActiveCookieJar();
const createGrpcRequest = useCreateGrpcRequest();
const createEnvironment = useCreateEnvironment();
const dialog = useDialog();
const workspaceId = useActiveWorkspaceId();
const activeEnvironment = useActiveEnvironment();
const [, setSidebarHidden] = useSidebarHidden();
const [command, setCommand] = useState<string>('');
const workspaceCommands = useMemo<CommandPaletteItem[]>(() => {
const commands: CommandPaletteItem[] = [
{
key: 'settings.open',
label: 'Open Settings',
action: 'settings.show',
onSelect: async () => {
if (workspaceId == null) return;
await invoke('cmd_new_nested_window', {
url: routes.paths.workspaceSettings({ workspaceId }),
label: 'settings',
title: 'Yaak Settings',
});
},
},
{
key: 'app.create',
label: 'Create Workspace',
onSelect: createWorkspace.mutate,
},
{
key: 'http_request.create',
label: 'Create HTTP Request',
onSelect: () => createHttpRequest.mutate({}),
},
{
key: 'cookies.show',
label: 'Show Cookies',
onSelect: async () => {
dialog.show({
id: 'cookies',
title: 'Manage Cookies',
size: 'full',
render: () => <CookieDialog cookieJarId={activeCookieJar?.id ?? null} />,
});
},
},
{
key: 'grpc_request.create',
label: 'Create GRPC Request',
onSelect: () => createGrpcRequest.mutate({}),
},
{
key: 'environment.edit',
label: 'Edit Environment',
action: 'environmentEditor.toggle',
onSelect: () => {
dialog.toggle({
id: 'environment-editor',
noPadding: true,
size: 'lg',
className: 'h-[80vh]',
render: () => <EnvironmentEditDialog initialEnvironment={activeEnvironment} />,
});
},
},
{
key: 'environment.create',
label: 'Create Environment',
onSelect: createEnvironment.mutate,
},
{
key: 'sidebar.toggle',
label: 'Toggle Sidebar',
action: 'sidebar.focus',
onSelect: () => setSidebarHidden((h) => !h),
},
];
return commands.sort((a, b) =>
('searchText' in a ? a.searchText : a.label).localeCompare(
'searchText' in b ? b.searchText : b.label,
),
);
}, [
activeCookieJar,
activeEnvironment,
createEnvironment.mutate,
createGrpcRequest,
createHttpRequest,
createWorkspace.mutate,
dialog,
routes.paths,
setSidebarHidden,
workspaceId,
]);
const sortedRequests = useMemo(() => {
return [...requests].sort((a, b) => {
const aRecentIndex = recentRequests.indexOf(a.id);
const bRecentIndex = recentRequests.indexOf(b.id);
if (aRecentIndex >= 0 && bRecentIndex >= 0) {
return aRecentIndex - bRecentIndex;
} else if (aRecentIndex >= 0 && bRecentIndex === -1) {
return -1;
} else if (aRecentIndex === -1 && bRecentIndex >= 0) {
return 1;
} else {
return a.createdAt.localeCompare(b.createdAt);
}
});
}, [recentRequests, requests]);
const sortedEnvironments = useMemo(() => {
return [...environments].sort((a, b) => {
const aRecentIndex = recentEnvironments.indexOf(a.id);
const bRecentIndex = recentEnvironments.indexOf(b.id);
if (aRecentIndex >= 0 && bRecentIndex >= 0) {
return aRecentIndex - bRecentIndex;
} else if (aRecentIndex >= 0 && bRecentIndex === -1) {
return -1;
} else if (aRecentIndex === -1 && bRecentIndex >= 0) {
return 1;
} else {
return a.createdAt.localeCompare(b.createdAt);
}
});
}, [environments, recentEnvironments]);
const sortedWorkspaces = useMemo(() => {
return [...workspaces].sort((a, b) => {
const aRecentIndex = recentWorkspaces.indexOf(a.id);
const bRecentIndex = recentWorkspaces.indexOf(b.id);
if (aRecentIndex >= 0 && bRecentIndex >= 0) {
return aRecentIndex - bRecentIndex;
} else if (aRecentIndex >= 0 && bRecentIndex === -1) {
return -1;
} else if (aRecentIndex === -1 && bRecentIndex >= 0) {
return 1;
} else {
return a.createdAt.localeCompare(b.createdAt);
}
});
}, [recentWorkspaces, workspaces]);
const groups = useMemo<CommandPaletteGroup[]>(() => {
const actionsGroup: CommandPaletteGroup = {
key: 'actions',
label: 'Actions',
items: workspaceCommands,
};
const requestGroup: CommandPaletteGroup = {
key: 'requests',
label: 'Requests',
items: [],
};
for (const r of sortedRequests) {
if (r.id === activeRequestId) {
continue;
}
requestGroup.items.push({
const items = useMemo<{ label: string; onSelect: () => void; key: string }[]>(() => {
const items = [];
for (const r of requests) {
items.push({
key: `switch-request-${r.id}`,
searchText: fallbackRequestName(r),
label: (
<HStack space={2}>
<HttpMethodTag className="text-fg-subtler" request={r} />
<div className="truncate">{fallbackRequestName(r)}</div>
</HStack>
),
label: `Switch Request → ${fallbackRequestName(r)}`,
onSelect: () => {
return routes.navigate('request', {
workspaceId: r.workspaceId,
@@ -247,79 +32,25 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
},
});
}
const environmentGroup: CommandPaletteGroup = {
key: 'environments',
label: 'Environments',
items: [],
};
for (const e of sortedEnvironments) {
if (e.id === activeEnvironment?.id) {
continue;
}
environmentGroup.items.push({
key: `switch-environment-${e.id}`,
label: e.name,
onSelect: () => routes.setEnvironment(e),
});
}
const workspaceGroup: CommandPaletteGroup = {
key: 'workspaces',
label: 'Workspaces',
items: [],
};
for (const w of sortedWorkspaces) {
if (w.id === active) {
continue;
}
workspaceGroup.items.push({
for (const w of workspaces) {
items.push({
key: `switch-workspace-${w.id}`,
label: w.name,
onSelect: () => openWorkspace.mutate({ workspace: w, inNewWindow: false }),
label: `Switch Workspace → ${w.name}`,
onSelect: async () => {
const environmentId = (await getRecentEnvironments(w.id))[0];
return routes.navigate('workspace', {
workspaceId: w.id,
environmentId,
});
},
});
}
return items;
}, [activeEnvironmentId, requests, routes, workspaces]);
return [actionsGroup, requestGroup, environmentGroup, workspaceGroup];
}, [
workspaceCommands,
sortedRequests,
activeRequestId,
routes,
activeEnvironmentId,
sortedEnvironments,
activeEnvironment?.id,
sortedWorkspaces,
active,
openWorkspace,
]);
const allItems = useMemo(() => groups.flatMap((g) => g.items), [groups]);
useEffect(() => {
setSelectedItemKey(null);
}, [command]);
const { filteredGroups, filteredAllItems } = useMemo(() => {
const result = command
? search(command, allItems, {
threshold: 0.5,
keySelector: (v) => ('searchText' in v ? v.searchText : v.label),
})
: allItems;
const filteredGroups = groups
.map((g) => {
g.items = result.filter((i) => g.items.includes(i)).slice(0, MAX_PER_GROUP);
return g;
})
.filter((g) => g.items.length > 0);
const filteredAllItems = filteredGroups.flatMap((g) => g.items);
return { filteredAllItems, filteredGroups };
}, [allItems, command, groups]);
const filteredItems = useMemo(() => {
return items.filter((v) => v.label.toLowerCase().includes(command.toLowerCase()));
}, [command, items]);
const handleSelectAndClose = useCallback(
(cb: () => void) => {
@@ -329,73 +60,44 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
[onClose],
);
const selectedItem = useMemo(() => {
let selectedItem = filteredAllItems.find((i) => i.key === selectedItemKey) ?? null;
if (selectedItem == null) {
selectedItem = filteredAllItems[0] ?? null;
}
return selectedItem;
}, [filteredAllItems, selectedItemKey]);
const handleKeyDown = useCallback(
(e: KeyboardEvent<HTMLInputElement>) => {
const index = filteredAllItems.findIndex((v) => v.key === selectedItem?.key);
if (e.key === 'ArrowDown' || (e.ctrlKey && e.key === 'n')) {
const next = filteredAllItems[index + 1] ?? filteredAllItems[0];
setSelectedItemKey(next?.key ?? null);
} else if (e.key === 'ArrowUp' || (e.ctrlKey && e.key === 'k')) {
const prev = filteredAllItems[index - 1] ?? filteredAllItems[filteredAllItems.length - 1];
setSelectedItemKey(prev?.key ?? null);
(e: KeyboardEvent) => {
if (e.key === 'ArrowDown') {
setSelectedIndex((prev) => prev + 1);
} else if (e.key === 'ArrowUp') {
setSelectedIndex((prev) => prev - 1);
} else if (e.key === 'Enter') {
const selected = filteredAllItems[index];
setSelectedItemKey(selected?.key ?? null);
if (selected) {
handleSelectAndClose(selected.onSelect);
const item = filteredItems[selectedIndex];
if (item) {
handleSelectAndClose(item.onSelect);
}
}
},
[filteredAllItems, handleSelectAndClose, selectedItem?.key],
[filteredItems, handleSelectAndClose, selectedIndex],
);
return (
<div className="h-full w-[400px] grid grid-rows-[auto_minmax(0,1fr)] overflow-hidden">
<div className="h-full grid grid-rows-[auto_minmax(0,1fr)]">
<div className="px-2 py-2 w-full">
<PlainInput
<Input
hideLabel
leftSlot={
<div className="h-md w-10 flex justify-center items-center">
<Icon icon="search" className="text-fg-subtle" />
</div>
}
name="command"
label="Command"
placeholder="Search or type a command"
className="font-sans !text-base"
defaultValue={command}
placeholder="Type a command"
defaultValue=""
onChange={setCommand}
onKeyDownCapture={handleKeyDown}
onKeyDown={handleKeyDown}
/>
</div>
<div className="h-full px-1.5 overflow-y-auto pb-1">
{filteredGroups.map((g) => (
<div key={g.key} className="mb-1.5 w-full">
<Heading size={2} className="!text-xs uppercase px-1.5 h-sm flex items-center">
{g.label}
</Heading>
{g.items.map((v) => (
<CommandPaletteItem
active={v.key === selectedItem?.key}
key={v.key}
onClick={() => handleSelectAndClose(v.onSelect)}
rightSlot={
v.action && <CommandPaletteAction action={v.action} onAction={v.onSelect} />
}
>
{v.label}
</CommandPaletteItem>
))}
</div>
<div className="h-full px-1.5 overflow-y-auto">
{filteredItems.map((v, i) => (
<CommandPaletteItem
active={i === selectedIndex}
key={v.key}
onClick={() => handleSelectAndClose(v.onSelect)}
>
{v.label}
</CommandPaletteItem>
))}
</div>
</div>
@@ -406,39 +108,20 @@ function CommandPaletteItem({
children,
active,
onClick,
rightSlot,
}: {
children: ReactNode;
active: boolean;
onClick: () => void;
rightSlot?: ReactNode;
}) {
return (
<Button
<button
onClick={onClick}
tabIndex={active ? undefined : -1}
rightSlot={rightSlot}
color="custom"
justify="start"
className={classNames(
'w-full h-sm flex items-center rounded px-1.5',
'hover:text-fg',
'w-full h-xs flex items-center rounded px-1.5 text-fg-subtle',
active && 'bg-background-highlight-secondary text-fg',
!active && 'text-fg-subtle',
)}
>
<span className="truncate">{children}</span>
</Button>
{children}
</button>
);
}
function CommandPaletteAction({
action,
onAction,
}: {
action: HotkeyAction;
onAction: () => void;
}) {
useHotKey(action, onAction);
return <HotKey className="ml-auto" action={action} />;
}

View File

@@ -21,7 +21,7 @@ import { Icon } from './core/Icon';
import { IconButton } from './core/IconButton';
import { InlineCode } from './core/InlineCode';
import type { PairEditorProps } from './core/PairEditor';
import { PairOrBulkEditor } from './core/PairOrBulkEditor';
import { PairEditor } from './core/PairEditor';
import { Separator } from './core/Separator';
import { SplitLayout } from './core/SplitLayout';
import { HStack, VStack } from './core/Stacks';
@@ -185,20 +185,18 @@ const EnvironmentEditor = function ({
/>
</Heading>
</HStack>
<div className="h-full pr-2 pb-2">
<PairOrBulkEditor
preferenceName="environment"
nameAutocomplete={nameAutocomplete}
nameAutocompleteVariables={false}
namePlaceholder="VAR_NAME"
nameValidate={validateName}
valueType={valueVisibility.value ? 'text' : 'password'}
valueAutocompleteVariables={false}
forceUpdateKey={environment?.id ?? workspace?.id ?? 'n/a'}
pairs={variables}
onChange={handleChange}
/>
</div>
<PairEditor
className="pr-2"
nameAutocomplete={nameAutocomplete}
nameAutocompleteVariables={false}
namePlaceholder="VAR_NAME"
nameValidate={validateName}
valueType={valueVisibility.value ? 'text' : 'password'}
valueAutocompleteVariables={false}
forceUpdateKey={environment?.id ?? workspace?.id ?? 'n/a'}
pairs={variables}
onChange={handleChange}
/>
</VStack>
);
};

View File

@@ -59,7 +59,7 @@ export function ExportDataDialog({
const noneSelected = numSelected === 0;
return (
<VStack space={3} className="w-full mb-3 px-4">
<table className="w-full mb-auto min-w-full max-w-full divide-y divide-background-highlight-secondary">
<table className="w-full mb-auto min-w-full max-w-full divide-y">
<thead>
<tr>
<th className="w-6 min-w-0 py-2 text-left pl-1">
@@ -76,7 +76,7 @@ export function ExportDataDialog({
</th>
</tr>
</thead>
<tbody className="divide-y divide-background-highlight-secondary">
<tbody className="divide-y">
{workspaces.map((w) => (
<tr key={w.id}>
<td className="min-w-0 py-1 pl-1">

View File

@@ -1,7 +1,7 @@
import { useCallback, useMemo } from 'react';
import type { HttpRequest } from '../lib/models';
import type { Pair, PairEditorProps } from './core/PairEditor';
import { PairOrBulkEditor } from './core/PairOrBulkEditor';
import { PairEditor } from './core/PairEditor';
type Props = {
forceUpdateKey: string;
@@ -27,8 +27,7 @@ export function FormUrlencodedEditor({ body, forceUpdateKey, onChange }: Props)
);
return (
<PairOrBulkEditor
preferenceName="form_urlencoded"
<PairEditor
valueAutocompleteVariables
nameAutocompleteVariables
namePlaceholder="entry_name"

View File

@@ -6,6 +6,7 @@ import { useCommandPalette } from '../hooks/useCommandPalette';
import { cookieJarsQueryKey } from '../hooks/useCookieJars';
import { environmentsQueryKey } from '../hooks/useEnvironments';
import { foldersQueryKey } from '../hooks/useFolders';
import { useGlobalCommands } from '../hooks/useGlobalCommands';
import { grpcConnectionsQueryKey } from '../hooks/useGrpcConnections';
import { grpcEventsQueryKey } from '../hooks/useGrpcEvents';
import { grpcRequestsQueryKey } from '../hooks/useGrpcRequests';
@@ -41,6 +42,7 @@ export function GlobalHooks() {
// Other useful things
useSyncThemeToDocument();
useGlobalCommands();
useCommandPalette();
useNotificationToast();

View File

@@ -125,7 +125,7 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi
{...extraEditorProps}
/>
<div className="grid grid-rows-[auto_minmax(0,1fr)] grid-cols-1 min-h-[5rem]">
<Separator dashed className="pb-1">
<Separator variant="primary" className="pb-1">
Variables
</Separator>
<Editor

View File

@@ -111,7 +111,7 @@ export function GrpcConnectionLayout({ style }: Props) {
) : messages.length >= 0 ? (
<GrpcConnectionMessagesPane activeRequest={activeRequest} methodType={methodType} />
) : (
<HotKeyList hotkeys={['grpc_request.send', 'sidebar.focus', 'urlBar.focus']} />
<HotKeyList hotkeys={['grpc_request.send', 'sidebar.toggle', 'urlBar.focus']} />
)}
</div>
)

View File

@@ -6,7 +6,7 @@ import { mimeTypes } from '../lib/data/mimetypes';
import type { HttpRequest } from '../lib/models';
import type { GenericCompletionConfig } from './core/Editor/genericCompletion';
import type { PairEditorProps } from './core/PairEditor';
import { PairOrBulkEditor } from './core/PairOrBulkEditor';
import { PairEditor } from './core/PairEditor';
type Props = {
forceUpdateKey: string;
@@ -16,8 +16,7 @@ type Props = {
export function HeadersEditor({ headers, onChange, forceUpdateKey }: Props) {
return (
<PairOrBulkEditor
preferenceName="headers"
<PairEditor
valueAutocompleteVariables
nameAutocompleteVariables
pairs={headers}

View File

@@ -1,64 +0,0 @@
import { useState } from 'react';
import { useOpenWorkspace } from '../hooks/useOpenWorkspace';
import { useSettings } from '../hooks/useSettings';
import { useUpdateSettings } from '../hooks/useUpdateSettings';
import type { Workspace } from '../lib/models';
import { Button } from './core/Button';
import { Checkbox } from './core/Checkbox';
import { Icon } from './core/Icon';
import { InlineCode } from './core/InlineCode';
import { HStack, VStack } from './core/Stacks';
interface Props {
hide: () => void;
workspace: Workspace;
}
export function OpenWorkspaceDialog({ hide, workspace }: Props) {
const openWorkspace = useOpenWorkspace();
const settings = useSettings();
const updateSettings = useUpdateSettings();
const [remember, setRemember] = useState<boolean>(false);
return (
<VStack space={3}>
<p>
Where would you like to open <InlineCode>{workspace.name}</InlineCode>?
</p>
<HStack space={2} justifyContent="start" className="flex-row-reverse">
<Button
className="focus"
color="primary"
onClick={() => {
hide();
openWorkspace.mutate({ workspace, inNewWindow: false });
if (remember) {
updateSettings.mutate({ openWorkspaceNewWindow: false });
}
}}
>
This Window
</Button>
<Button
className="focus"
color="secondary"
rightSlot={<Icon icon="externalLink" />}
onClick={() => {
hide();
openWorkspace.mutate({ workspace, inNewWindow: true });
if (remember) {
updateSettings.mutate({ openWorkspaceNewWindow: true });
}
}}
>
New Window
</Button>
</HStack>
{settings && (
<HStack justifyContent="end">
<Checkbox checked={remember} title="Remember my choice" onChange={setRemember} />
</HStack>
)}
</VStack>
);
}

View File

@@ -47,13 +47,13 @@ export function Overlay({
variant === 'default' && 'bg-background-backdrop backdrop-blur-sm',
)}
/>
{children}
{/* Show draggable region at the top */}
{/* TODO: Figure out tauri drag region and also make clickable still */}
{variant === 'default' && (
<div data-tauri-drag-region className="absolute top-0 left-0 h-md right-0" />
)}
{children}
</motion.div>
</FocusTrap>
)}

View File

@@ -1,7 +1,6 @@
import type { ReactNode } from 'react';
import { createPortal } from 'react-dom';
import { usePortal } from '../hooks/usePortal';
interface Props {
children: ReactNode;
name: string;

View File

@@ -53,7 +53,7 @@ export function RecentRequestsDropdown({ className }: Pick<ButtonProps, 'classNa
key: request.id,
label: fallbackRequestName(request),
// leftSlot: <CountBadge className="!ml-0 px-0 w-5" count={recentRequestItems.length} />,
leftSlot: <HttpMethodTag className="text-right" shortNames request={request} />,
leftSlot: <HttpMethodTag request={request} />,
onSelect: () => {
routes.navigate('request', {
requestId: request.id,

View File

@@ -1,7 +1,6 @@
import classNames from 'classnames';
import { useDeleteHttpResponse } from '../hooks/useDeleteHttpResponse';
import { useDeleteHttpResponses } from '../hooks/useDeleteHttpResponses';
import { useSaveResponse } from '../hooks/useSaveResponse';
import type { HttpResponse } from '../lib/models';
import { pluralize } from '../lib/pluralize';
import { Dropdown } from './core/Dropdown';
@@ -26,49 +25,30 @@ export const RecentResponsesDropdown = function ResponsePane({
const deleteResponse = useDeleteHttpResponse(activeResponse?.id ?? null);
const deleteAllResponses = useDeleteHttpResponses(activeResponse?.requestId);
const latestResponseId = responses[0]?.id ?? 'n/a';
const saveResponse = useSaveResponse(activeResponse);
return (
<Dropdown
items={[
{
key: 'save',
label: 'Save to File',
onSelect: saveResponse.mutate,
leftSlot: <Icon icon="save" />,
hidden: responses.length === 0,
disabled: responses.length === 0,
},
{
key: 'clear-single',
label: 'Delete',
leftSlot: <Icon icon="trash" />,
label: 'Clear Response',
onSelect: deleteResponse.mutate,
disabled: responses.length === 0,
},
{
key: 'unpin',
label: 'Unpin Response',
onSelect: () => onPinnedResponseId(activeResponse.id),
leftSlot: <Icon icon="unpin" />,
hidden: latestResponseId === activeResponse.id,
disabled: responses.length === 0,
},
{ type: 'separator', label: 'History' },
{
key: 'clear-all',
label: `Delete ${responses.length} ${pluralize('Response', responses.length)}`,
label: `Clear ${responses.length} ${pluralize('Response', responses.length)}`,
onSelect: deleteAllResponses.mutate,
hidden: responses.length <= 1,
disabled: responses.length === 0,
},
{ type: 'separator' },
{ type: 'separator', label: 'History' },
...responses.slice(0, 20).map((r: HttpResponse) => ({
key: r.id,
label: (
<HStack space={2}>
<StatusTag className="text-sm" response={r} />
<span className="text-fg-subtle">&rarr;</span>{' '}
<span>&rarr;</span>{' '}
<span className="font-mono text-sm">{r.elapsed >= 0 ? `${r.elapsed}ms` : 'n/a'}</span>
</HStack>
),

View File

@@ -262,15 +262,13 @@ export const RequestPane = memo(function RequestPane({
options:
requests.length > 0
? [
...requests
.filter((r) => r.id !== activeRequestId)
.map(
(r) =>
({
type: 'constant',
label: r.url,
} as GenericCompletionOption),
),
...requests.map(
(r) =>
({
type: 'constant',
label: r.url,
} as GenericCompletionOption),
),
]
: [
{ label: 'http://', type: 'constant' },

View File

@@ -23,7 +23,6 @@ import { ResponseHeaders } from './ResponseHeaders';
import { AudioViewer } from './responseViewers/AudioViewer';
import { CsvViewer } from './responseViewers/CsvViewer';
import { ImageViewer } from './responseViewers/ImageViewer';
import { PdfViewer } from './responseViewers/PdfViewer';
import { TextViewer } from './responseViewers/TextViewer';
import { VideoViewer } from './responseViewers/VideoViewer';
import { WebPageViewer } from './responseViewers/WebPageViewer';
@@ -84,7 +83,7 @@ export const ResponsePane = memo(function ResponsePane({ style, className, activ
>
{activeResponse == null ? (
<HotKeyList
hotkeys={['http_request.send', 'http_request.create', 'sidebar.focus', 'urlBar.focus']}
hotkeys={['http_request.send', 'http_request.create', 'sidebar.toggle', 'urlBar.focus']}
/>
) : isResponseLoading(activeResponse) ? (
<div className="h-full w-full flex items-center justify-center">
@@ -159,12 +158,12 @@ export const ResponsePane = memo(function ResponsePane({ style, className, activ
<AudioViewer response={activeResponse} />
) : contentType?.startsWith('video') ? (
<VideoViewer response={activeResponse} />
) : contentType?.match(/pdf/) ? (
<PdfViewer response={activeResponse} />
) : contentType?.match(/csv|tab-separated/) ? (
<CsvViewer className="pb-2" response={activeResponse} />
) : activeResponse.contentLength > 2 * 1000 * 1000 ? (
<EmptyStateText>Cannot preview text responses larger than 2MB</EmptyStateText>
) : viewMode === 'pretty' && contentType?.includes('html') ? (
<WebPageViewer response={activeResponse} />
) : contentType?.match(/csv|tab-separated/) ? (
<CsvViewer className="pb-2" response={activeResponse} />
) : (
<TextViewer
className="-mr-2" // Pull to the right

View File

@@ -50,33 +50,6 @@ export function SettingsGeneral() {
onClick={() => checkForUpdates.mutateAsync()}
/>
</div>
<Select
name="openWorkspace"
label="Open Workspace"
labelPosition="left"
size="sm"
value={
settings.openWorkspaceNewWindow === true
? 'new'
: settings.openWorkspaceNewWindow === false
? 'current'
: 'ask'
}
onChange={(v) => {
if (v === 'current') {
updateSettings.mutate({ openWorkspaceNewWindow: false });
} else if (v === 'new') {
updateSettings.mutate({ openWorkspaceNewWindow: true });
} else {
updateSettings.mutate({ openWorkspaceNewWindow: null });
}
}}
options={[
{ label: 'Always Ask', value: 'ask' },
{ label: 'Current Window', value: 'current' },
{ label: 'New Window', value: 'new' },
]}
/>
<Separator className="my-4" />
<Heading size={2}>

View File

@@ -101,7 +101,7 @@ export function Sidebar({ className }: Props) {
[collapsed.value],
);
const { tree, treeParentMap, selectableRequests } = useMemo<{
const { tree, treeParentMap, selectableRequests, selectedRequest } = useMemo<{
tree: TreeNode | null;
treeParentMap: Record<string, TreeNode>;
selectedRequest: HttpRequest | GrpcRequest | null;
@@ -159,6 +159,8 @@ export function Sidebar({ className }: Props) {
return { tree, treeParentMap, selectableRequests, selectedRequest };
}, [activeWorkspace, selectedId, requests, folders, collapsed.value]);
const deleteSelectedRequest = useDeleteRequest(selectedRequest);
const focusActiveRequest = useCallback(
(
args: {
@@ -190,7 +192,7 @@ export function Sidebar({ className }: Props) {
);
const handleSelect = useCallback(
async (id: string, opts: { noFocus?: boolean } = {}) => {
async (id: string) => {
const tree = treeParentMap[id ?? 'n/a'] ?? null;
const children = tree?.children ?? [];
const node = children.find((m) => m.item.id === id) ?? null;
@@ -210,7 +212,7 @@ export function Sidebar({ className }: Props) {
});
setSelectedId(id);
setSelectedTree(tree);
if (!opts.noFocus) focusActiveRequest({ forced: { id, tree } });
focusActiveRequest({ forced: { id, tree } });
}
},
[treeParentMap, collapsed, routes, activeEnvironmentId, focusActiveRequest],
@@ -228,6 +230,21 @@ export function Sidebar({ className }: Props) {
const handleBlur = useCallback(() => setHasFocus(false), []);
const handleDeleteKey = useCallback(
async (e: KeyboardEvent) => {
if (!hasFocus) return;
e.preventDefault();
const selected = selectableRequests.find((r) => r.id === selectedId);
if (selected == null) return;
await deleteSelectedRequest.mutateAsync();
},
[hasFocus, selectableRequests, deleteSelectedRequest, selectedId],
);
useKeyPressEvent('Backspace', handleDeleteKey);
useKeyPressEvent('Delete', handleDeleteKey);
useHotKey('sidebar.focus', async () => {
// Hide the sidebar if it's already focused
if (!hidden && hasFocus) {
@@ -603,7 +620,7 @@ const SidebarItem = forwardRef(function SidebarItem(
) {
const activeRequest = useActiveRequest();
const deleteFolder = useDeleteFolder(itemId);
const deleteRequest = useDeleteRequest(itemId);
const deleteRequest = useDeleteRequest(activeRequest ?? null);
const duplicateHttpRequest = useDuplicateHttpRequest({ id: itemId, navigateAfter: true });
const duplicateGrpcRequest = useDuplicateGrpcRequest({ id: itemId, navigateAfter: true });
const copyAsCurl = useCopyAsCurl(itemId);
@@ -822,7 +839,7 @@ const SidebarItem = forwardRef(function SidebarItem(
)}
/>
)}
<div className="flex items-center gap-2 min-w-0">
<div className="flex items-end gap-2 min-w-0">
{itemPrefix}
{editing ? (
<input

View File

@@ -31,6 +31,7 @@ export function SidebarActions() {
className="pointer-events-auto"
size="sm"
title="Show sidebar"
hotkeyAction="sidebar.toggle"
icon={hidden ? 'leftPanelHidden' : 'leftPanelVisible'}
/>
<CreateDropdown hotKeyAction="http_request.create">

View File

@@ -67,7 +67,7 @@ export const ToastProvider = ({ children }: { children: React.ReactNode }) => {
<ToastContext.Provider value={state}>
{children}
<Portal name="toasts">
<div className="absolute right-0 bottom-0 z-10">
<div className="absolute right-0 bottom-0">
<AnimatePresence>
{toasts.map((props: PrivateToastEntry) => (
<ToastInstance key={props.id} {...props} />

View File

@@ -1,5 +1,5 @@
import type { HttpRequest } from '../lib/models';
import { PairOrBulkEditor } from './core/PairOrBulkEditor';
import { PairEditor } from './core/PairEditor';
type Props = {
forceUpdateKey: string;
@@ -9,8 +9,7 @@ type Props = {
export function UrlParametersEditor({ urlParameters, forceUpdateKey, onChange }: Props) {
return (
<PairOrBulkEditor
preferenceName="url_parameters"
<PairEditor
valueAutocompleteVariables
nameAutocompleteVariables
namePlaceholder="param_name"

View File

@@ -186,7 +186,7 @@ export default function Workspace() {
</div>
) : activeRequest == null ? (
<HotKeyList
hotkeys={['http_request.create', 'sidebar.focus', 'settings.show']}
hotkeys={['http_request.create', 'sidebar.toggle', 'settings.show']}
bottomSlot={
<HStack space={1} justifyContent="center" className="mt-3">
<Button variant="border" size="sm" onClick={() => importData.mutate()}>

View File

@@ -1,11 +1,13 @@
import { invoke } from '@tauri-apps/api/core';
import classNames from 'classnames';
import { memo, useMemo } from 'react';
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
import { useCreateWorkspace } from '../hooks/useCreateWorkspace';
import { useAppRoutes } from '../hooks/useAppRoutes';
import { useCommand } from '../hooks/useCommands';
import { useDeleteWorkspace } from '../hooks/useDeleteWorkspace';
import { useOpenWorkspace } from '../hooks/useOpenWorkspace';
import { usePrompt } from '../hooks/usePrompt';
import { useSettings } from '../hooks/useSettings';
import { getRecentEnvironments } from '../hooks/useRecentEnvironments';
import { getRecentRequests } from '../hooks/useRecentRequests';
import { useUpdateWorkspace } from '../hooks/useUpdateWorkspace';
import { useWorkspaces } from '../hooks/useWorkspaces';
import type { ButtonProps } from './core/Button';
@@ -14,8 +16,8 @@ import type { DropdownItem } from './core/Dropdown';
import { Dropdown } from './core/Dropdown';
import { Icon } from './core/Icon';
import { InlineCode } from './core/InlineCode';
import { HStack } from './core/Stacks';
import { useDialog } from './DialogContext';
import { OpenWorkspaceDialog } from './OpenWorkspaceDialog';
type Props = Pick<ButtonProps, 'className' | 'justify' | 'forDropdown' | 'leftSlot'>;
@@ -28,12 +30,10 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
const activeWorkspaceId = activeWorkspace?.id ?? null;
const updateWorkspace = useUpdateWorkspace(activeWorkspaceId);
const deleteWorkspace = useDeleteWorkspace(activeWorkspace);
const createWorkspace = useCreateWorkspace();
const createWorkspace = useCommand('workspace.create');
const dialog = useDialog();
const prompt = usePrompt();
const settings = useSettings();
const openWorkspace = useOpenWorkspace();
const openWorkspaceNewWindow = settings?.openWorkspaceNewWindow ?? null;
const routes = useAppRoutes();
const items: DropdownItem[] = useMemo(() => {
const workspaceItems: DropdownItem[] = workspaces.map((w) => ({
@@ -41,16 +41,58 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
label: w.name,
leftSlot: w.id === activeWorkspaceId ? <Icon icon="check" /> : <Icon icon="empty" />,
onSelect: async () => {
if (typeof openWorkspaceNewWindow === 'boolean') {
openWorkspace.mutate({ workspace: w, inNewWindow: openWorkspaceNewWindow });
return;
}
dialog.show({
id: 'open-workspace',
size: 'sm',
title: 'Open Workspace',
render: ({ hide }) => <OpenWorkspaceDialog workspace={w} hide={hide} />,
description: (
<>
Where would you like to open <InlineCode>{w.name}</InlineCode>?
</>
),
render: ({ hide }) => {
return (
<HStack space={2} justifyContent="start" className="mt-4 mb-6 flex-row-reverse">
<Button
className="focus"
color="primary"
onClick={async () => {
hide();
const environmentId = (await getRecentEnvironments(w.id))[0];
const requestId = (await getRecentRequests(w.id))[0];
if (requestId != null) {
routes.navigate('request', { workspaceId: w.id, environmentId, requestId });
} else {
routes.navigate('workspace', { workspaceId: w.id, environmentId });
}
}}
>
This Window
</Button>
<Button
className="focus"
color="secondary"
rightSlot={<Icon icon="externalLink" />}
onClick={async () => {
hide();
const environmentId = (await getRecentEnvironments(w.id))[0];
const requestId = (await getRecentRequests(w.id))[0];
const path =
requestId != null
? routes.paths.request({
workspaceId: w.id,
environmentId,
requestId,
})
: routes.paths.workspace({ workspaceId: w.id, environmentId });
await invoke('cmd_new_window', { url: path });
}}
>
New Window
</Button>
</HStack>
);
},
});
},
}));
@@ -101,7 +143,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
key: 'create-workspace',
label: 'New Workspace',
leftSlot: <Icon icon="plus" />,
onSelect: createWorkspace.mutate,
onSelect: () => createWorkspace.mutate({}),
},
];
}, [
@@ -110,9 +152,8 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
createWorkspace,
deleteWorkspace.mutate,
dialog,
openWorkspace,
prompt,
openWorkspaceNewWindow,
routes,
updateWorkspace,
workspaces,
]);

View File

@@ -1,45 +0,0 @@
import { useCallback, useMemo } from 'react';
import { Editor } from './Editor';
import type { PairEditorProps } from './PairEditor';
type Props = PairEditorProps;
export function BulkPairEditor({ pairs, onChange, namePlaceholder, valuePlaceholder }: Props) {
const pairsText = useMemo(() => {
return pairs
.filter((p) => !(p.name.trim() === '' && p.value.trim() === ''))
.map((p) => `${p.name}: ${p.value}`)
.join('\n');
}, [pairs]);
const handleChange = useCallback(
(text: string) => {
const pairs = text
.split('\n')
.filter((l: string) => l.trim())
.map(lineToPair);
onChange(pairs);
},
[onChange],
);
return (
<Editor
useTemplating
autocompleteVariables
placeholder={`${namePlaceholder ?? 'name'}: ${valuePlaceholder ?? 'value'}`}
defaultValue={pairsText}
contentType="pairs"
onChange={handleChange}
/>
);
}
function lineToPair(l: string): PairEditorProps['pairs'][0] {
const [name, ...values] = l.split(':');
const pair: PairEditorProps['pairs'][0] = {
name: (name ?? '').trim(),
value: values.join(':').trim(),
};
return pair;
}

View File

@@ -18,7 +18,6 @@ export interface DialogProps {
hideX?: boolean;
noPadding?: boolean;
noScroll?: boolean;
vAlign?: 'top' | 'center';
}
export function Dialog({
@@ -32,7 +31,6 @@ export function Dialog({
hideX,
noPadding,
noScroll,
vAlign = 'center',
}: DialogProps) {
const titleId = useMemo(() => Math.random().toString(36).slice(2), []);
const descriptionId = useMemo(
@@ -52,14 +50,13 @@ export function Dialog({
return (
<Overlay open={open} onClose={onClose} portalName="dialog">
<div
className={classNames(
'x-theme-dialog absolute inset-0 flex flex-col items-center pointer-events-none',
vAlign === 'top' && 'justify-start',
vAlign === 'center' && 'justify-center',
)}
>
<div role="dialog" aria-labelledby={titleId} aria-describedby={descriptionId}>
<div className="x-theme-dialog absolute inset-0 flex items-center justify-center pointer-events-none">
<div
role="dialog"
aria-labelledby={titleId}
aria-describedby={descriptionId}
className="pointer-events-auto"
>
<motion.div
initial={{ top: 5, scale: 0.97 }}
animate={{ top: 0, scale: 1 }}
@@ -69,12 +66,12 @@ export function Dialog({
'relative bg-background pointer-events-auto',
'rounded-lg',
'border border-background-highlight-secondary shadow-lg shadow-[rgba(0,0,0,0.1)]',
'max-w-[calc(100vw-5rem)] max-h-[calc(100vh-4rem)]',
size === 'sm' && 'w-[28rem] max-h-[80vh]',
'max-w-[calc(100vw-5rem)] max-h-[calc(100vh-6rem)]',
size === 'sm' && 'w-[25rem] max-h-[80vh]',
size === 'md' && 'w-[45rem] max-h-[80vh]',
size === 'lg' && 'w-[65rem] max-h-[80vh]',
size === 'full' && 'w-[100vw] h-[100vh]',
size === 'dynamic' && 'min-w-[20rem] max-w-[100vw] w-full mt-8',
size === 'dynamic' && 'min-w-[30vw] max-w-[80vw]',
)}
>
{title ? (

View File

@@ -35,7 +35,6 @@ import { HStack, VStack } from './Stacks';
export type DropdownItemSeparator = {
type: 'separator';
label?: string;
hidden?: boolean;
};
export type DropdownItemDefault = {
@@ -274,7 +273,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
let nextIndex = (currIndex ?? 0) - 1;
const maxTries = items.length;
for (let i = 0; i < maxTries; i++) {
if (items[nextIndex]?.hidden || items[nextIndex]?.type === 'separator') {
if (items[nextIndex]?.type === 'separator') {
nextIndex--;
} else if (nextIndex < 0) {
nextIndex = items.length - 1;
@@ -291,7 +290,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
let nextIndex = (currIndex ?? -1) + 1;
const maxTries = items.length;
for (let i = 0; i < maxTries; i++) {
if (items[nextIndex]?.hidden || items[nextIndex]?.type === 'separator') {
if (items[nextIndex]?.type === 'separator') {
nextIndex++;
} else if (nextIndex >= items.length) {
nextIndex = 0;
@@ -374,7 +373,6 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
right: onRight ? docRect.width - triggerShape.right : undefined,
left: !onRight ? triggerShape.left : undefined,
minWidth: fullWidth ? triggerWidth : undefined,
maxWidth: '40rem',
};
const size = { top: '-0.2rem', width: '0.4rem', height: '0.4rem' };
const triangleStyles = onRight
@@ -457,9 +455,6 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
<span className="text-fg-subtler text-center px-2 py-1">No matches</span>
)}
{filteredItems.map((item, i) => {
if (item.hidden) {
return null;
}
if (item.type === 'separator') {
return (
<Separator key={i} className={classNames('my-1.5', item.label && 'ml-2')}>
@@ -467,6 +462,9 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
</Separator>
);
}
if (item.hidden) {
return null;
}
return (
<MenuItem
focused={i === selectedIndex}
@@ -539,12 +537,20 @@ function MenuItem({ className, focused, onFocus, item, onSelect, ...props }: Men
'h-xs', // More compact
'min-w-[8rem] outline-none px-2 mx-1.5 flex whitespace-nowrap',
'focus:bg-background-highlight focus:text-fg rounded',
item.variant === 'danger' && '!text-fg-danger',
item.variant === 'notify' && '!text-fg-primary',
item.variant === 'default' && 'text-fg-subtle',
item.variant === 'danger' && 'text-fg-danger',
item.variant === 'notify' && 'text-fg-primary',
)}
{...props}
>
<div className={classNames('truncate')}>{item.label}</div>
<div
className={classNames(
// Add padding on right when no right slot, for some visual balance
!item.rightSlot && 'pr-4',
)}
>
{item.label}
</div>
</Button>
);
}

View File

@@ -102,7 +102,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
// Use ref so we can update the handler without re-initializing the editor
const handleChange = useRef<EditorProps['onChange']>(onChange);
useEffect(() => {
handleChange.current = onChange ? onChange : onChange;
handleChange.current = onChange;
}, [onChange]);
// Use ref so we can update the handler without re-initializing the editor
@@ -332,20 +332,6 @@ function getExtensions({
return [
...baseExtensions, // Must be first
EditorView.domEventHandlers({
focus: () => {
onFocus.current?.();
},
blur: () => {
onBlur.current?.();
},
keydown: (e) => {
onKeyDown.current?.(e);
},
paste: (e) => {
onPaste.current?.(e.clipboardData?.getData('text/plain') ?? '');
},
}),
tooltips({ parent }),
keymap.of(singleLine ? defaultKeymap.filter((k) => k.key !== 'Enter') : defaultKeymap),
...(singleLine ? [singleLineExt()] : []),
@@ -363,6 +349,21 @@ function getExtensions({
onChange.current?.(update.state.doc.toString());
}
}),
EditorView.domEventHandlers({
focus: () => {
onFocus.current?.();
},
blur: () => {
onBlur.current?.();
},
keydown: (e) => {
onKeyDown.current?.(e);
},
paste: (e) => {
onPaste.current?.(e.clipboardData?.getData('text/plain') ?? '');
},
}),
];
}

View File

@@ -35,7 +35,6 @@ import { graphql, graphqlLanguageSupport } from 'cm6-graphql';
import { EditorView } from 'codemirror';
import type { Environment, Workspace } from '../../../lib/models';
import type { EditorProps } from './index';
import { pairs } from './pairs/extension';
import { text } from './text/extension';
import { twig } from './twig/extension';
import { url } from './url/extension';
@@ -72,7 +71,6 @@ const syntaxExtensions: Record<string, LanguageSupport> = {
'application/xml': xml(),
'text/xml': xml(),
url: url(),
pairs: pairs(),
};
export function getLanguageExtension({

View File

@@ -1,11 +0,0 @@
import { LanguageSupport, LRLanguage } from '@codemirror/language';
import { parser } from './pairs';
const urlLanguage = LRLanguage.define({
parser,
languageData: {},
});
export function pairs() {
return new LanguageSupport(urlLanguage, []);
}

View File

@@ -1,7 +0,0 @@
import { styleTags, tags as t } from '@lezer/highlight';
export const highlight = styleTags({
Sep: t.bracket,
Key: t.attributeName,
Value: t.string,
});

View File

@@ -1,9 +0,0 @@
@top pairs { (Key? Sep Value)* }
@tokens {
Sep { ":" }
Key { ![:]+ }
Value { ![\n]+ }
}
@external propSource highlight from "./highlight"

View File

@@ -1,19 +0,0 @@
// This file was generated by lezer-generator. You probably shouldn't edit it.
import {LRParser} from "@lezer/lr"
import {highlight} from "./highlight"
export const parser = LRParser.deserialize({
version: 14,
states: "!QQQOPOOOYOQO'#CaO_OPO'#CaQQOPOOOOOO,58{,58{OdOQO,58{OOOO-E6_-E6_OOOO1G.g1G.g",
stateData: "i~OQQORPO~OSSO~ORTO~OSVO~O",
goto: "]UPPPPPVQRORUR",
nodeNames: "⚠ pairs Key Sep Value",
maxTerm: 6,
propSources: [highlight],
skippedNodes: [0],
repeatNodeCount: 1,
tokenData: "#oRRVOYhYZ!UZ![h![!]#[!];'Sh;'S;=`#U<%lOhRoVQPSQOYhYZ!UZ![h![!]!m!];'Sh;'S;=`#U<%lOhP!ZSQPO![!U!];'S!U;'S;=`!g<%lO!UP!jP;=`<%l!UQ!rSSQOY!mZ;'S!m;'S;=`#O<%lO!mQ#RP;=`<%l!mR#XP;=`<%lhR#cSRPSQOY!mZ;'S!m;'S;=`#O<%lO!m",
tokenizers: [0, 1],
topRules: {"pairs":[0,1]},
tokenPrec: 0
})

Some files were not shown because too many files have changed in this diff Show More