Skip to content

Commit 0bff1ca

Browse files
committed
V14 Integrations (Semrush)
- Start implementing UI for Semrush
1 parent 7a3b393 commit 0bff1ca

23 files changed

+7341
-4
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
pnpm-debug.log*
8+
lerna-debug.log*
9+
10+
node_modules
11+
dist
12+
dist-ssr
13+
*.local
14+
15+
# Editor directories and files
16+
.vscode/*
17+
!.vscode/extensions.json
18+
.idea
19+
.DS_Store
20+
*.suo
21+
*.ntvs*
22+
*.njsproj
23+
*.sln
24+
*.sw?
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const outputPath = 'Debug' !== 'Release' ? '../wwwroot' : '../obj/Debug/net8.0/clientassets'
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { ApiRequestOptions } from './ApiRequestOptions';
2+
import type { ApiResult } from './ApiResult';
3+
4+
export class ApiError extends Error {
5+
public readonly url: string;
6+
public readonly status: number;
7+
public readonly statusText: string;
8+
public readonly body: unknown;
9+
public readonly request: ApiRequestOptions;
10+
11+
constructor(request: ApiRequestOptions, response: ApiResult, message: string) {
12+
super(message);
13+
14+
this.name = 'ApiError';
15+
this.url = response.url;
16+
this.status = response.status;
17+
this.statusText = response.statusText;
18+
this.body = response.body;
19+
this.request = request;
20+
}
21+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export type ApiRequestOptions = {
2+
readonly method: 'GET' | 'PUT' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH';
3+
readonly url: string;
4+
readonly path?: Record<string, unknown>;
5+
readonly cookies?: Record<string, unknown>;
6+
readonly headers?: Record<string, unknown>;
7+
readonly query?: Record<string, unknown>;
8+
readonly formData?: Record<string, unknown>;
9+
readonly body?: any;
10+
readonly mediaType?: string;
11+
readonly responseHeader?: string;
12+
readonly errors?: Record<number | string, string>;
13+
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export type ApiResult<TData = any> = {
2+
readonly body: TData;
3+
readonly ok: boolean;
4+
readonly status: number;
5+
readonly statusText: string;
6+
readonly url: string;
7+
};
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
export class CancelError extends Error {
2+
constructor(message: string) {
3+
super(message);
4+
this.name = 'CancelError';
5+
}
6+
7+
public get isCancelled(): boolean {
8+
return true;
9+
}
10+
}
11+
12+
export interface OnCancel {
13+
readonly isResolved: boolean;
14+
readonly isRejected: boolean;
15+
readonly isCancelled: boolean;
16+
17+
(cancelHandler: () => void): void;
18+
}
19+
20+
export class CancelablePromise<T> implements Promise<T> {
21+
private _isResolved: boolean;
22+
private _isRejected: boolean;
23+
private _isCancelled: boolean;
24+
readonly cancelHandlers: (() => void)[];
25+
readonly promise: Promise<T>;
26+
private _resolve?: (value: T | PromiseLike<T>) => void;
27+
private _reject?: (reason?: unknown) => void;
28+
29+
constructor(
30+
executor: (
31+
resolve: (value: T | PromiseLike<T>) => void,
32+
reject: (reason?: unknown) => void,
33+
onCancel: OnCancel
34+
) => void
35+
) {
36+
this._isResolved = false;
37+
this._isRejected = false;
38+
this._isCancelled = false;
39+
this.cancelHandlers = [];
40+
this.promise = new Promise<T>((resolve, reject) => {
41+
this._resolve = resolve;
42+
this._reject = reject;
43+
44+
const onResolve = (value: T | PromiseLike<T>): void => {
45+
if (this._isResolved || this._isRejected || this._isCancelled) {
46+
return;
47+
}
48+
this._isResolved = true;
49+
if (this._resolve) this._resolve(value);
50+
};
51+
52+
const onReject = (reason?: unknown): void => {
53+
if (this._isResolved || this._isRejected || this._isCancelled) {
54+
return;
55+
}
56+
this._isRejected = true;
57+
if (this._reject) this._reject(reason);
58+
};
59+
60+
const onCancel = (cancelHandler: () => void): void => {
61+
if (this._isResolved || this._isRejected || this._isCancelled) {
62+
return;
63+
}
64+
this.cancelHandlers.push(cancelHandler);
65+
};
66+
67+
Object.defineProperty(onCancel, 'isResolved', {
68+
get: (): boolean => this._isResolved,
69+
});
70+
71+
Object.defineProperty(onCancel, 'isRejected', {
72+
get: (): boolean => this._isRejected,
73+
});
74+
75+
Object.defineProperty(onCancel, 'isCancelled', {
76+
get: (): boolean => this._isCancelled,
77+
});
78+
79+
return executor(onResolve, onReject, onCancel as OnCancel);
80+
});
81+
}
82+
83+
get [Symbol.toStringTag]() {
84+
return "Cancellable Promise";
85+
}
86+
87+
public then<TResult1 = T, TResult2 = never>(
88+
onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
89+
onRejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null
90+
): Promise<TResult1 | TResult2> {
91+
return this.promise.then(onFulfilled, onRejected);
92+
}
93+
94+
public catch<TResult = never>(
95+
onRejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null
96+
): Promise<T | TResult> {
97+
return this.promise.catch(onRejected);
98+
}
99+
100+
public finally(onFinally?: (() => void) | null): Promise<T> {
101+
return this.promise.finally(onFinally);
102+
}
103+
104+
public cancel(): void {
105+
if (this._isResolved || this._isRejected || this._isCancelled) {
106+
return;
107+
}
108+
this._isCancelled = true;
109+
if (this.cancelHandlers.length) {
110+
try {
111+
for (const cancelHandler of this.cancelHandlers) {
112+
cancelHandler();
113+
}
114+
} catch (error) {
115+
console.warn('Cancellation threw an error', error);
116+
return;
117+
}
118+
}
119+
this.cancelHandlers.length = 0;
120+
if (this._reject) this._reject(new CancelError('Request aborted'));
121+
}
122+
123+
public get isCancelled(): boolean {
124+
return this._isCancelled;
125+
}
126+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import type { ApiRequestOptions } from './ApiRequestOptions';
2+
3+
type Headers = Record<string, string>;
4+
type Middleware<T> = (value: T) => T | Promise<T>;
5+
type Resolver<T> = (options: ApiRequestOptions) => Promise<T>;
6+
7+
export class Interceptors<T> {
8+
_fns: Middleware<T>[];
9+
10+
constructor() {
11+
this._fns = [];
12+
}
13+
14+
eject(fn: Middleware<T>): void {
15+
const index = this._fns.indexOf(fn);
16+
if (index !== -1) {
17+
this._fns = [...this._fns.slice(0, index), ...this._fns.slice(index + 1)];
18+
}
19+
}
20+
21+
use(fn: Middleware<T>): void {
22+
this._fns = [...this._fns, fn];
23+
}
24+
}
25+
26+
export type OpenAPIConfig = {
27+
BASE: string;
28+
CREDENTIALS: 'include' | 'omit' | 'same-origin';
29+
ENCODE_PATH?: ((path: string) => string) | undefined;
30+
HEADERS?: Headers | Resolver<Headers> | undefined;
31+
PASSWORD?: string | Resolver<string> | undefined;
32+
TOKEN?: string | Resolver<string> | undefined;
33+
USERNAME?: string | Resolver<string> | undefined;
34+
VERSION: string;
35+
WITH_CREDENTIALS: boolean;
36+
interceptors: {
37+
request: Interceptors<RequestInit>;
38+
response: Interceptors<Response>;
39+
};
40+
};
41+
42+
export const OpenAPI: OpenAPIConfig = {
43+
BASE: '',
44+
CREDENTIALS: 'include',
45+
ENCODE_PATH: undefined,
46+
HEADERS: undefined,
47+
PASSWORD: undefined,
48+
TOKEN: undefined,
49+
USERNAME: undefined,
50+
VERSION: 'Latest',
51+
WITH_CREDENTIALS: false,
52+
interceptors: {
53+
request: new Interceptors(),
54+
response: new Interceptors(),
55+
},
56+
};

0 commit comments

Comments
 (0)