Skip to content

Commit c89166e

Browse files
committed
fix(code-review): hooks, style files and documentation
1 parent 312c26a commit c89166e

File tree

16 files changed

+208
-177
lines changed

16 files changed

+208
-177
lines changed

src/app/header/hooks.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { KeyboardEventHandler } from "react";
2+
import { StringParam, useQueryParams } from "use-query-params";
3+
import { useHistory, useLocation } from "react-router-dom";
4+
import * as qs from "query-string";
5+
6+
// FIXME: get from `pages`?
7+
const SEARCH_URL = "/search";
8+
9+
/**
10+
* @hook Логика обработки инпута поиска
11+
*/
12+
export const useSearchInput = () => {
13+
// !!! FIXME: limit scope of query-params literals
14+
const [query] = useQueryParams({
15+
q: StringParam,
16+
type: StringParam,
17+
s: StringParam,
18+
o: StringParam,
19+
});
20+
const location = useLocation();
21+
const history = useHistory();
22+
23+
/**
24+
* Обработка инпута поиска
25+
*/
26+
const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = ({ key, currentTarget }) => {
27+
if (key === "Enter" && currentTarget.value) {
28+
const q = currentTarget.value;
29+
history.push(`${SEARCH_URL}?${qs.stringify({ ...query, q })}`);
30+
}
31+
};
32+
/**
33+
* Поисковой запрос
34+
* @remark Если не страница поиска - обнуляем инпут
35+
*/
36+
const searchValue = location.pathname === SEARCH_URL ? query.q ?? "" : "";
37+
38+
return { handleKeyDown, searchValue };
39+
};

src/app/header/index.tsx

Lines changed: 3 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,12 @@
1-
import React, { KeyboardEventHandler } from "react";
1+
import React from "react";
22
import { Layout, Input } from "antd";
3-
import { Link, useHistory, useLocation } from "react-router-dom";
4-
import { StringParam, useQueryParams } from "use-query-params";
5-
import * as qs from "query-string";
3+
import { Link } from "react-router-dom";
64
import { GITHUB_MAIN, GITHUB_FEEDBACK } from "shared/get-env";
75
import { Auth } from "features";
86
import { ReactComponent as IcLogo } from "./logo.svg";
7+
import { useSearchInput } from "./hooks";
98
import "./index.scss";
109

11-
// FIXME: get from `pages`?
12-
const SEARCH_URL = "/search";
13-
14-
/**
15-
* @hook Логика обработки инпута поиска
16-
*/
17-
const useSearchInput = () => {
18-
// !!! FIXME: limit scope of query-params literals
19-
const [query] = useQueryParams({
20-
q: StringParam,
21-
type: StringParam,
22-
s: StringParam,
23-
o: StringParam,
24-
});
25-
const location = useLocation();
26-
const history = useHistory();
27-
28-
/**
29-
* Обработка инпута поиска
30-
*/
31-
const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = ({ key, currentTarget }) => {
32-
if (key === "Enter" && currentTarget.value) {
33-
const q = currentTarget.value;
34-
history.push(`${SEARCH_URL}?${qs.stringify({ ...query, q })}`);
35-
}
36-
};
37-
/**
38-
* Поисковой запрос
39-
* @remark Если не страница поиска - обнуляем инпут
40-
*/
41-
const searchValue = location.pathname === SEARCH_URL ? query.q ?? "" : "";
42-
43-
return { handleKeyDown, searchValue };
44-
};
45-
4610
/**
4711
* Хедер приложения
4812
* @remark Содержит поисковой инпут с базовой логикой

src/app/index.scss

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
// NOTE: Временно используем вспомогательные utilities-классы, на время разработки прототипа приложения
22
@import "~tailwindcss/dist/utilities.css";
3-
@import "./vars.scss";
4-
@import "./normalize.scss";
5-
@import "./normalize-antd.scss";
6-
@import "./utils.scss";
3+
@import "./styles/vars.scss";
4+
@import "./styles/normalize.scss";
5+
@import "./styles/normalize-antd.scss";
6+
@import "./styles/utils.scss";
77

88
.gc-app {
99
display: flex;

src/features/auth/user/index.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from "react";
22
import { Button } from "antd";
3+
import { Link } from "react-router-dom";
34
import { useAuth } from "../hooks";
45
import { routes } from "../consts";
56

@@ -18,9 +19,9 @@ const User = () => {
1819
{isAuth && (
1920
<>
2021
{/* FIXME: use h3 instead */}
21-
<a className="m-4 text-white" href={`/${viewer?.username}`}>
22+
<Link className="m-4 text-white" to={`/${viewer?.username}`}>
2223
{viewer?.username}
23-
</a>
24+
</Link>
2425
<Button className="m-4" href={routes.logout} onClick={logout}>
2526
Logout
2627
</Button>
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { BranchIdentity, RepoIdentity } from "models";
2+
import { RepoBranchInfoQuery, useRepoDefaultBranchQuery } from "../queries.gen";
3+
4+
type Props = {
5+
repo: RepoIdentity;
6+
};
7+
8+
/**
9+
* @hook Получение текущей ветки репозитория
10+
*/
11+
export const useBranch = (repo: Props["repo"]) => {
12+
const { data } = useRepoDefaultBranchQuery({
13+
variables: {
14+
name: repo.name,
15+
owner: repo.owner,
16+
},
17+
});
18+
const branch = repo.branch || data?.repository?.defaultBranchRef?.name || "master";
19+
return { branch };
20+
};
21+
22+
/**
23+
* @hook Получение нужных полей по сфетченным данным по репозиторию
24+
*/
25+
export const useRepoDetails = (repoInfo: RepoBranchInfoQuery | undefined) => {
26+
const { repository } = repoInfo || {};
27+
const branches = (repository?.refs?.nodes || []).filter(
28+
(branch): branch is BranchIdentity => !!branch,
29+
);
30+
const files = Array.from(repository?.object?.entries ?? []).sort((a, b) =>
31+
b.type.localeCompare(a.type),
32+
);
33+
const target = repository?.ref?.target;
34+
const lastCommit =
35+
(target && {
36+
message: target.messageHeadline,
37+
login: target.author?.user?.login,
38+
avatarUrl: target.author?.user?.avatarUrl,
39+
name: target.author?.name,
40+
date: target.author?.date,
41+
}) ||
42+
undefined;
43+
44+
// Приходится фетчить файл по двум вариантам наименования, т.к. GitHub не умеет в insensitive case =(
45+
const readme = repository?.contentLower?.text || repository?.contentUpper?.text || "";
46+
return {
47+
branches,
48+
files,
49+
lastCommit,
50+
readme,
51+
};
52+
};
53+
54+
type useLocalUriProps = {
55+
/** Текст README файла */
56+
text: string;
57+
/** Флаг загрузки */
58+
loading: boolean;
59+
/**
60+
* Ссылка-идентификатор репозитория
61+
* @remark Для обработки локальных ссылок
62+
*/
63+
repoUrl: string;
64+
/**
65+
* Текущая ветка
66+
* @remark Для обработки локальных ссылок
67+
*/
68+
branch: string;
69+
};
70+
71+
/**
72+
* @hook Обработка внутренних ссылок
73+
* @remark
74+
* - В README могут быть указаны ссылки на локальные ресурсы репозитория (images, files, anchors, ...)
75+
* - Поэтому, для корректной навигации и отображения, было решено предобрабатывать подобные ссылки
76+
*/
77+
export const useLocalUri = ({ repoUrl, branch }: useLocalUriProps) => {
78+
/**
79+
* Нормализация внутренних ссылок
80+
* @example
81+
* transformLocalUri("https://some-url/...")
82+
* // => "https://some-url/..."
83+
* transformLocalUri("#some-header")
84+
* // => "https://github.com/${repo}#some-header"
85+
* transformLocalUri("./SOMEFILE.md")
86+
* // => "https://github.com/${repo}/blobk/${branch}/SOMEFILE.md"
87+
* transformLocalUri("docs/ANOTHER.md")
88+
* // => "https://github.com/${repo}/blobk/${branch}/docs/ANOTHER.md"
89+
*/
90+
const transformLinkUri = (uri: string) => {
91+
if (uri.startsWith("http")) return uri;
92+
if (uri.startsWith("#")) return `https://github.com/${repoUrl}${uri}`;
93+
// Если sibling-link - нормализуем
94+
const blobUrl = uri.replace("./", "");
95+
return `https://github.com/${repoUrl}/blob/${branch}/${blobUrl}`;
96+
};
97+
98+
/**
99+
* Получение исходника локального изображения
100+
* FIXME: Работает только с markodwn-изображениями, потом переделать бы на общий случай
101+
* @example
102+
* transformImageUri("docs/search.png")
103+
* // => https://raw.githubusercontent.com/${repo}/${branch}/docs/search.png
104+
*/
105+
const transformImageUri = (uri: string) => {
106+
if (uri.startsWith("http")) return uri;
107+
// Если sibling-link - нормализуем
108+
const blobUrl = uri.replace("./", "");
109+
return `https://raw.githubusercontent.com/${repoUrl}/${branch}/${blobUrl}`;
110+
};
111+
112+
return { transformLinkUri, transformImageUri };
113+
};

src/features/repo-explorer/components/index.tsx

Lines changed: 3 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,15 @@
11
import React from "react";
2-
import { RepoIdentity, BranchIdentity } from "models";
3-
import {
4-
useRepoBranchInfoQuery,
5-
useRepoDefaultBranchQuery,
6-
RepoBranchInfoQuery,
7-
} from "../queries.gen";
2+
import { RepoIdentity } from "models";
3+
import { useRepoBranchInfoQuery } from "../queries.gen";
84
import RepoToolbar from "./toolbar";
95
import EntriesView from "./entries-view";
106
import RepoReadme from "./readme";
7+
import { useBranch, useRepoDetails } from "./hooks";
118

129
type Props = {
1310
repo: RepoIdentity;
1411
};
1512

16-
/**
17-
* @hook Получение текущей ветки репозитория
18-
*/
19-
const useBranch = (repo: Props["repo"]) => {
20-
const { data } = useRepoDefaultBranchQuery({
21-
variables: {
22-
name: repo.name,
23-
owner: repo.owner,
24-
},
25-
});
26-
const branch = repo.branch || data?.repository?.defaultBranchRef?.name || "master";
27-
return { branch };
28-
};
29-
30-
/**
31-
* @hook Получение нужных полей по сфетченным данным по репозиторию
32-
*/
33-
const useRepoDetails = (repoInfo: RepoBranchInfoQuery | undefined) => {
34-
const { repository } = repoInfo || {};
35-
const branches = (repository?.refs?.nodes || []).filter(
36-
(branch): branch is BranchIdentity => !!branch,
37-
);
38-
const files = Array.from(repository?.object?.entries ?? []).sort((a, b) =>
39-
b.type.localeCompare(a.type),
40-
);
41-
const target = repository?.ref?.target;
42-
const lastCommit =
43-
(target && {
44-
message: target.messageHeadline,
45-
login: target.author?.user?.login,
46-
avatarUrl: target.author?.user?.avatarUrl,
47-
name: target.author?.name,
48-
date: target.author?.date,
49-
}) ||
50-
undefined;
51-
52-
// Приходится фетчить файл по двум вариантам наименования, т.к. GitHub не умеет в insensitive case =(
53-
const readme = repository?.contentLower?.text || repository?.contentUpper?.text || "";
54-
return {
55-
branches,
56-
files,
57-
lastCommit,
58-
readme,
59-
};
60-
};
61-
6213
const Explorer = ({ repo }: Props) => {
6314
const { branch } = useBranch(repo);
6415
const { loading, data } = useRepoBranchInfoQuery({

0 commit comments

Comments
 (0)