2024-08-25 00:45:44 +00:00
|
|
|
import { type ClassValue, clsx } from "clsx"
|
|
|
|
|
import { twMerge } from "tailwind-merge"
|
2024-09-07 01:24:39 +00:00
|
|
|
import githubLogo from "../../public/github.svg";
|
|
|
|
|
import gitlabLogo from "../../public/gitlab.svg";
|
2024-10-23 03:06:36 +00:00
|
|
|
import giteaLogo from "../../public/gitea.svg";
|
2024-09-10 06:16:41 +00:00
|
|
|
import { ServiceError } from "./serviceError";
|
2024-11-27 05:49:41 +00:00
|
|
|
import { Repository } from "./types";
|
2024-08-25 00:45:44 +00:00
|
|
|
|
|
|
|
|
export function cn(...inputs: ClassValue[]) {
|
2024-09-07 01:24:39 +00:00
|
|
|
return twMerge(clsx(inputs))
|
2024-08-25 00:45:44 +00:00
|
|
|
}
|
2024-08-25 02:39:59 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Adds a list of (potentially undefined) query parameters to a path.
|
|
|
|
|
*
|
|
|
|
|
* @param path The path to add the query parameters to.
|
|
|
|
|
* @param queryParams A list of key-value pairs (key=param name, value=param value) to add to the path.
|
|
|
|
|
* @returns The path with the query parameters added.
|
|
|
|
|
*/
|
|
|
|
|
export const createPathWithQueryParams = (path: string, ...queryParams: [string, string | null][]) => {
|
|
|
|
|
// Filter out undefined values
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
|
|
|
queryParams = queryParams.filter(([_key, value]) => value !== null);
|
|
|
|
|
|
|
|
|
|
if (queryParams.length === 0) {
|
|
|
|
|
return path;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const queryString = queryParams.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value ?? '')}`).join('&');
|
|
|
|
|
return `${path}?${queryString}`;
|
2024-09-07 01:24:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type CodeHostInfo = {
|
2024-10-23 03:06:36 +00:00
|
|
|
type: "github" | "gitlab" | "gitea";
|
2024-11-27 05:49:41 +00:00
|
|
|
displayName: string;
|
2024-09-07 01:24:39 +00:00
|
|
|
costHostName: string;
|
|
|
|
|
repoLink: string;
|
|
|
|
|
icon: string;
|
2024-10-28 17:30:29 +00:00
|
|
|
iconClassName?: string;
|
2024-09-07 01:24:39 +00:00
|
|
|
}
|
|
|
|
|
|
2024-11-27 05:49:41 +00:00
|
|
|
export const getRepoCodeHostInfo = (repo?: Repository): CodeHostInfo | undefined => {
|
|
|
|
|
if (!repo) {
|
|
|
|
|
return undefined;
|
2024-09-07 01:24:39 +00:00
|
|
|
}
|
|
|
|
|
|
2024-11-27 05:49:41 +00:00
|
|
|
const hostType = repo.RawConfig ? repo.RawConfig['web-url-type'] : undefined;
|
|
|
|
|
if (!hostType) {
|
|
|
|
|
return undefined;
|
2024-09-07 01:24:39 +00:00
|
|
|
}
|
|
|
|
|
|
2024-11-27 05:49:41 +00:00
|
|
|
const url = new URL(repo.URL);
|
|
|
|
|
const displayName = url.pathname.slice(1);
|
2024-09-07 01:24:39 +00:00
|
|
|
|
2024-11-27 05:49:41 +00:00
|
|
|
switch (hostType) {
|
|
|
|
|
case 'github':
|
|
|
|
|
return {
|
|
|
|
|
type: "github",
|
|
|
|
|
displayName: displayName,
|
|
|
|
|
costHostName: "GitHub",
|
|
|
|
|
repoLink: repo.URL,
|
|
|
|
|
icon: githubLogo,
|
|
|
|
|
iconClassName: "dark:invert",
|
|
|
|
|
}
|
|
|
|
|
case 'gitlab':
|
|
|
|
|
return {
|
|
|
|
|
type: "gitlab",
|
|
|
|
|
displayName: displayName,
|
|
|
|
|
costHostName: "GitLab",
|
|
|
|
|
repoLink: repo.URL,
|
|
|
|
|
icon: gitlabLogo,
|
|
|
|
|
}
|
|
|
|
|
case 'gitea':
|
|
|
|
|
return {
|
|
|
|
|
type: "gitea",
|
|
|
|
|
displayName: displayName,
|
|
|
|
|
costHostName: "Gitea",
|
|
|
|
|
repoLink: repo.URL,
|
|
|
|
|
icon: giteaLogo,
|
|
|
|
|
}
|
2024-10-23 03:06:36 +00:00
|
|
|
}
|
2024-09-10 06:16:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const isServiceError = (data: unknown): data is ServiceError => {
|
|
|
|
|
return typeof data === 'object' &&
|
|
|
|
|
data !== null &&
|
|
|
|
|
'statusCode' in data &&
|
|
|
|
|
'errorCode' in data &&
|
|
|
|
|
'message' in data;
|
|
|
|
|
}
|
2024-09-17 04:37:34 +00:00
|
|
|
|
2024-11-26 05:04:52 +00:00
|
|
|
export const getEnv = (env: string | undefined, defaultValue?: string) => {
|
2024-09-17 04:37:34 +00:00
|
|
|
return env ?? defaultValue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const getEnvNumber = (env: string | undefined, defaultValue: number = 0) => {
|
|
|
|
|
return Number(env) ?? defaultValue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const getEnvBoolean = (env: string | undefined, defaultValue: boolean) => {
|
|
|
|
|
if (!env) {
|
|
|
|
|
return defaultValue;
|
|
|
|
|
}
|
|
|
|
|
return env === 'true' || env === '1';
|
2024-10-16 00:33:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// From https://developer.mozilla.org/en-US/docs/Glossary/Base64#the_unicode_problem
|
|
|
|
|
export const base64Decode = (base64: string): string => {
|
|
|
|
|
const binString = atob(base64);
|
|
|
|
|
return Buffer.from(Uint8Array.from(binString, (m) => m.codePointAt(0)!).buffer).toString();
|
|
|
|
|
}
|
Search suggestions (#85)
The motivation for building search suggestions is two-fold: (1) to make the zoekt query language more approachable by presenting all available options to the user, and (2) make it easier for power-users to craft complex queries.
The meat-n-potatoes of this change are concentrated in searchBar.tsx and searchSuggestionBox.tsx. The suggestions box works by maintaining a state-machine of "modes". By default, the box is in the refine mode, where suggestions for different prefixes (e.g., repo:, lang:, etc.) are suggested to the user. When one of these prefixes is matched, the state-machine transitions to the corresponding mode (e.g., repository, language, etc.) and surfaces suggestions for that mode (if any).
The query is split up into parts by spaces " " (e.g., 'test repo:hello' -> ['test', 'repo:hello']). See splitQuery. The part that has the cursor over it is considered the active part. We evaluate which mode the state machine is in based on the active part. When a suggestion is clicked, we only modify the active part of the query.
Three modes are currently missing suggestion data: file (file names), revision (branch / tag names), and symbol (symbol names). In future PRs, we will need to introduce endpoints into the backend to allow the frontend to fetch this data and surface it as suggestions..
2024-11-23 02:50:13 +00:00
|
|
|
|
|
|
|
|
// @see: https://stackoverflow.com/a/65959350/23221295
|
|
|
|
|
export const isDefined = <T>(arg: T | null | undefined): arg is T extends null | undefined ? never : T => {
|
|
|
|
|
return arg !== null && arg !== undefined;
|
|
|
|
|
}
|